<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  

  
  <title>山芋头</title>
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
  <meta name="description" content="记录和总结让知识得到积累，分享能创造价值">
<meta property="og:type" content="website">
<meta property="og:title" content="山芋头">
<meta property="og:url" content="https://bianchengme.gitee.io/index.html">
<meta property="og:site_name" content="山芋头">
<meta property="og:description" content="记录和总结让知识得到积累，分享能创造价值">
<meta property="og:locale" content="cn">
<meta property="article:author" content="jmme@foxmail.com">
<meta name="twitter:card" content="summary">
  
    <link rel="alternate" href="/atom.xml" title="山芋头" type="application/atom+xml">
  
  
    <link rel="icon" href="/favicon.png">
  
  
    <link href="//fonts.googleapis.com/css?family=Source+Code+Pro" rel="stylesheet" type="text/css">
  
  
<link rel="stylesheet" href="/css/style.css">

<meta name="generator" content="Hexo 4.2.0"></head>

<body>
  <div id="container">
    <div id="wrap">
      <header id="header">
  <div id="banner"></div>
  <div id="header-outer" class="outer">
    <div id="header-title" class="inner">
      <h1 id="logo-wrap">
        <a href="/" id="logo">山芋头</a>
      </h1>
      
        <h2 id="subtitle-wrap">
          <a href="/" id="subtitle">记录和分享能创造价值</a>
        </h2>
      
    </div>
    <div id="header-inner" class="inner">
      <nav id="main-nav">
        <a id="main-nav-toggle" class="nav-icon"></a>
        
          <a class="main-nav-link" href="/">Home</a>
        
          <a class="main-nav-link" href="/archives">Archives</a>
        
      </nav>
      <nav id="sub-nav">
        
          <a id="nav-rss-link" class="nav-icon" href="/atom.xml" title="RSS Feed"></a>
        
        <a id="nav-search-btn" class="nav-icon" title="Search"></a>
      </nav>
      <div id="search-form-wrap">
        <form action="//google.com/search" method="get" accept-charset="UTF-8" class="search-form"><input type="search" name="q" class="search-form-input" placeholder="Search"><button type="submit" class="search-form-submit">&#xF002;</button><input type="hidden" name="sitesearch" value="https://bianchengme.gitee.io"></form>
      </div>
    </div>
  </div>
</header>
      <div class="outer">
        <section id="main">
  
    <article id="post-JVM-逃逸分析优化" class="article article-type-post" itemscope itemprop="blogPost">
  <div class="article-meta">
    <a href="/2021/09/07/JVM-%E9%80%83%E9%80%B8%E5%88%86%E6%9E%90%E4%BC%98%E5%8C%96/" class="article-date">
  <time datetime="2021-09-07T02:52:06.000Z" itemprop="datePublished">2021-09-07</time>
</a>
    
  <div class="article-category">
    <a class="article-category-link" href="/categories/JAVA/">JAVA</a>►<a class="article-category-link" href="/categories/JAVA/JVM/">JVM</a>
  </div>

  </div>
  <div class="article-inner">
    
    
      <header class="article-header">
        
  
    <h1 itemprop="name">
      <a class="article-title" href="/2021/09/07/JVM-%E9%80%83%E9%80%B8%E5%88%86%E6%9E%90%E4%BC%98%E5%8C%96/">JVM 逃逸分析优化</a>
    </h1>
  

      </header>
    
    <div class="article-entry" itemprop="articleBody">
      
        <blockquote>
<p>“Java 中的对象是否都分配在堆内存中？”<br>——“不尽然”</p>
</blockquote>
<p>Java 中对象的创建一般会由堆内存去分配内存空间来进行存储，在堆内存空间不足的时候，GC 便会对堆内存进行垃圾回收，如果 GC 运行的次数过多，便会影响程序的性能,并且堆内存为所有线程共享，存再同步问题；所以 <strong>“逃逸分析”</strong> 由此诞生，它的目的就是判断哪些对象是可以存储在栈内存中而不用存储在堆内存中的，从而让其随着线程的消逝而消逝，进而减少了 GC 发生的频率，这也是常见的 JVM 优化技巧之一。</p>
<h1 id="什么是逃逸分析"><a href="#什么是逃逸分析" class="headerlink" title="什么是逃逸分析"></a>什么是逃逸分析</h1><p>逃逸分析（Escape Analysis）简单来讲就是，Java Hotspot 虚拟机可以分析新创建对象的使用范围，并决定是否在 Java 堆上分配内存的一项技术。</p>
<p>在方法中创建对象之后，如果这个对象 <strong>实例</strong> 除了在方法体中还在其它地方被引用了，此时如果方法执行完毕，由于该对象有被引用，所以 GC 有可能是无法立即回收的，此时便成为 <strong>内存逃逸现象</strong>。</p>
<h1 id="如何使用逃逸分析"><a href="#如何使用逃逸分析" class="headerlink" title="如何使用逃逸分析"></a>如何使用逃逸分析</h1><ul>
<li>开启逃逸分析：<code>-XX:+DoEscapeAnalysis</code></li>
<li>关闭逃逸分析：<code>-XX:-DoEscapeAnalysis</code></li>
<li>显示分析结果：<code>-XX:+PrintEscapeAnalysis</code></li>
</ul>
<p>逃逸分析的作用，就是筛选出没有发生逃逸的对象，从而对它们进行以下三方面的优化：</p>
<h3 id="同步消除（锁消除）"><a href="#同步消除（锁消除）" class="headerlink" title="同步消除（锁消除）"></a>同步消除（锁消除）</h3><p>因为同步锁是非常消耗性能的，所以当编译器确定一个对象没有发生逃逸时，它便会移除该对象的同步锁。</p>
<p>在 JDK1.8 中是默认开启的，但是要建立在已开启逃逸分析的基础之上。</p>
<ul>
<li>开启锁消除：<code>-XX:+EliminateLocks</code>（JDK8默认开启）</li>
<li>关闭锁消除：<code>-XX:-EliminateLocks</code></li>
</ul>
<h3 id="标量替换"><a href="#标量替换" class="headerlink" title="标量替换"></a>标量替换</h3><p>首先要明白标量和聚合量，基础类型和对象的引用可以理解为标量，它们不能被进一步分解。而能被进一步分解的量就是聚合量，比如：对象。</p>
<p>对象是聚合量，它又可以被进一步分解成标量，将其成员变量分解为分散的变量，这就叫做标量替换。</p>
<p>这样，如果一个对象没有发生逃逸，那压根就不用创建它，只会在栈或者寄存器上创建它用到的成员标量，节省了内存空间，也提升了应用程序性能。</p>
<p>标量替换在 JDK1.8 中也是默认开启的，但是同样也要建立在已开启逃逸分析的基础之上。</p>
<ul>
<li>开启标量替换：<code>-XX:+EliminateAllocations</code></li>
<li>关闭标量替换：<code>-XX:-EliminateAllocations</code></li>
<li>显示标量替换详情：<code>-XX:+PrintEliminateAllocations</code></li>
</ul>
<h3 id="栈内存分配"><a href="#栈内存分配" class="headerlink" title="栈内存分配"></a>栈内存分配</h3><p>栈内存分配很好理解，在上文中提过，就是将原本分配在堆内存上的对象转而分配在栈内存上，这样就可以减少堆内存的占用，从而减少 GC 的频次。</p>
<h1 id="逃逸分析的优势"><a href="#逃逸分析的优势" class="headerlink" title="逃逸分析的优势"></a>逃逸分析的优势</h1><p>我们通过如下代码测试：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> shanyutou.github.io</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@ClassName</span>: EscapeAnalysisTest</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Description</span>: https://shanyutou.github.io 逸出分析测试</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@date</span> 2021/09/07</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Version</span>: 1.0.0</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">EscapeAnalysisTest</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> InterruptedException </span>&#123;</span><br><span class="line">        <span class="keyword">long</span> t1 = System.currentTimeMillis();</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; <span class="number">100000000</span>; i++) &#123;</span><br><span class="line">            allot();</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">long</span> t2 = System.currentTimeMillis();</span><br><span class="line">        System.out.println(t2-t1);</span><br><span class="line"></span><br><span class="line">        Thread.sleep(Integer.MAX_VALUE);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">allot</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        Jet jet = <span class="keyword">new</span> Jet();</span><br><span class="line"><span class="comment">//        synchronized (jet) &#123;</span></span><br><span class="line"><span class="comment">//            jet.name = "jet Chen";</span></span><br><span class="line"><span class="comment">//        &#125;</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">Jet</span> </span>&#123;</span><br><span class="line">        <span class="keyword">public</span> String name;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h4 id="Step-1：测试开启逃逸"><a href="#Step-1：测试开启逃逸" class="headerlink" title="Step 1：测试开启逃逸"></a>Step 1：测试开启逃逸</h4><p>环境是 jdk1.8，默认开启了逃逸分析，所以直接运行，得到结果如下，程序耗时 3 毫秒，没有发生GC。</p>
<p><img src="E:%5Chexo%5Chexo_boke%5Csource_posts%5CJVM-%E9%80%83%E9%80%B8%E5%88%86%E6%9E%90%E4%BC%98%E5%8C%96%5C01.png" alt=""></p>
<h4 id="Step-2：测试关闭逃逸"><a href="#Step-2：测试关闭逃逸" class="headerlink" title="Step 2：测试关闭逃逸"></a>Step 2：测试关闭逃逸</h4><blockquote>
<p>-XX:-DoEscapeAnalysis</p>
</blockquote>
<p>得到结果如下，程序耗时 312 毫秒产生一次GC。</p>
<p><img src="E:%5Chexo%5Chexo_boke%5Csource_posts%5CJVM-%E9%80%83%E9%80%B8%E5%88%86%E6%9E%90%E4%BC%98%E5%8C%96%5C02.png" alt=""></p>
<p>所以，从代码的执行时间（3 毫秒 VS 400 毫秒），还是从GC分析，在上述场景下，开启逃逸分析是有正向益的。</p>
<h4 id="Step-3：测试标量替换"><a href="#Step-3：测试标量替换" class="headerlink" title="Step 3：测试标量替换"></a>Step 3：测试标量替换</h4><blockquote>
<p>-XX:+DoEscapeAnalysis -XX:+EliminateAllocations  //耗时4<br>-XX:+DoEscapeAnalysis -XX:-EliminateAllocations  //耗时306<br>-XX:-DoEscapeAnalysis -XX:+EliminateAllocations  //耗时310<br>-XX:-DoEscapeAnalysis -XX:-EliminateAllocations  //耗时310</p>
</blockquote>
<p>在上述极端场景下，开启和关闭标量替换对于性能的影响也是满巨大的，另外，同时也验证了标量替换功能生效的前提是逃逸分析已经开启，否则没有意义。同时也可发现如果开启逃逸分析，但是没有启动标量替换，并无太多优化；可以得出结论 <strong>Hotspot JVM 并没有直接将未逃逸的对象分配到栈空间，而实际是用标量替换实现优化。</strong>结论Hotspot对象还是分配在堆内存。</p>
<h4 id="Step-4：测试锁消除"><a href="#Step-4：测试锁消除" class="headerlink" title="Step 4：测试锁消除"></a>Step 4：测试锁消除</h4><p>测试锁消除，我们需要简单调整下代码，即给 allot() 方法中的内容加锁处理，如下：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">allot</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    Jet jet = <span class="keyword">new</span> Jet();</span><br><span class="line">    <span class="keyword">synchronized</span> (jet) &#123;</span><br><span class="line">        jet.name = <span class="string">"jet Chen"</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<blockquote>
<p>-XX:+DoEscapeAnalysis -XX:+EliminateLocks  //耗时4<br>-XX:+DoEscapeAnalysis -XX:-EliminateLocks  //耗时308<br>-XX:-DoEscapeAnalysis -XX:+EliminateLocks  //耗时1492<br>-XX:-DoEscapeAnalysis -XX:-EliminateLocks  //耗时1493<br>然后我们运行测试代码，测试结果也很明显，在上述场景下，开启和关闭锁消除对程序性能的影响也是巨大的。同时也验证了标量替换功能生效的前提是逃逸分析已经开启，否则没有意义。</p>
</blockquote>
<h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>逃逸分析的原理理解起来其实很简单，但 JVM 在实际应用过程中，还是有诸多因素需要考虑的。</p>
<p>比如，逃逸分析不能在静态编译时进行，必须在 JIT 里完成。原因大致是：与 Java 的动态性有冲突。因为你可以在运行时，通过动态代理改变一个类的行为，此时，逃逸分析是无法得知类已经变化了。总之就是：因为只有当收集到足够的运行数据时，JVM 才可以更好地判断对象是否发生了逃逸。（参考大佬的解释：<a href="https://www.zhihu.com/ques....）" target="_blank" rel="noopener">https://www.zhihu.com/ques....）</a></p>
<p>当然，逃逸分析并不是没有劣势的，因为逃逸分析是需要消耗一定的性能去执行分析的，所以说如果方法中的对象全都是处于逃逸状态，那么就没有起到优化的作用，从而就白白损失了这部分的性能消耗。</p>

      
    </div>
    <footer class="article-footer">
      <a data-url="https://bianchengme.gitee.io/2021/09/07/JVM-%E9%80%83%E9%80%B8%E5%88%86%E6%9E%90%E4%BC%98%E5%8C%96/" data-id="cktcaskgv00083gucgkrj6gc1" class="article-share-link">Share</a>
      
      
  <ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/JAVA/" rel="tag">JAVA</a></li><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/JVM/" rel="tag">JVM</a></li><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/%E4%BC%98%E5%8C%96/" rel="tag">优化</a></li></ul>

    </footer>
  </div>
  
</article>


  
    <article id="post-Nacos通过Docke部署" class="article article-type-post" itemscope itemprop="blogPost">
  <div class="article-meta">
    <a href="/2021/07/02/Nacos%E9%80%9A%E8%BF%87Docke%E9%83%A8%E7%BD%B2/" class="article-date">
  <time datetime="2021-07-02T01:28:53.000Z" itemprop="datePublished">2021-07-02</time>
</a>
    
  <div class="article-category">
    <a class="article-category-link" href="/categories/Docker/">Docker</a>►<a class="article-category-link" href="/categories/Docker/Nacos/">Nacos</a>
  </div>

  </div>
  <div class="article-inner">
    
    
      <header class="article-header">
        
  
    <h1 itemprop="name">
      <a class="article-title" href="/2021/07/02/Nacos%E9%80%9A%E8%BF%87Docke%E9%83%A8%E7%BD%B2/">Nacos通过Docke部署</a>
    </h1>
  

      </header>
    
    <div class="article-entry" itemprop="articleBody">
      
        <h2 id="方式一"><a href="#方式一" class="headerlink" title="方式一"></a>方式一</h2><h6 id="1-拉取镜像"><a href="#1-拉取镜像" class="headerlink" title="1.拉取镜像"></a>1.拉取镜像</h6><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker pull nacos/nacos-server</span><br></pre></td></tr></table></figure>

<h6 id="2-挂载目录"><a href="#2-挂载目录" class="headerlink" title="2.挂载目录"></a>2.挂载目录</h6><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">mkdir -p ~/data/nacos/logs/ ~/data/nacos/init.d/   <span class="comment">#创建目录          </span></span><br><span class="line">vim ~/data/nacos/init.d/application.properties        <span class="comment">#修改配置文件</span></span><br></pre></td></tr></table></figure>

<p>添加如下参数:</p>
<figure class="highlight properties"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">server.contextPath</span>=<span class="string">/nacos</span></span><br><span class="line"><span class="meta">server.servlet.contextPath</span>=<span class="string">/nacos</span></span><br><span class="line"><span class="meta">server.port</span>=<span class="string">8848</span></span><br><span class="line"></span><br><span class="line"><span class="meta">spring.datasource.platform</span>=<span class="string">mysql</span></span><br><span class="line"></span><br><span class="line"><span class="meta">db.num</span>=<span class="string">1</span></span><br><span class="line"><span class="meta">db.url.0</span>=<span class="string">jdbc:mysql://xx.xx.xx.x:3306/nacos_devtest_prod?characterEncoding=utf8&amp;connectTimeout=1000&amp;socketTimeout=3000&amp;autoReconnect=true</span></span><br><span class="line"><span class="meta">db.user</span>=<span class="string">user</span></span><br><span class="line"><span class="meta">db.password</span>=<span class="string">pass</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">nacos.cmdb.dumpTaskInterval</span>=<span class="string">3600</span></span><br><span class="line"><span class="meta">nacos.cmdb.eventTaskInterval</span>=<span class="string">10</span></span><br><span class="line"><span class="meta">nacos.cmdb.labelTaskInterval</span>=<span class="string">300</span></span><br><span class="line"><span class="meta">nacos.cmdb.loadDataAtStart</span>=<span class="string">false</span></span><br><span class="line"></span><br><span class="line"><span class="meta">management.metrics.export.elastic.enabled</span>=<span class="string">false</span></span><br><span class="line"></span><br><span class="line"><span class="meta">management.metrics.export.influx.enabled</span>=<span class="string">false</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">server.tomcat.accesslog.enabled</span>=<span class="string">true</span></span><br><span class="line"><span class="meta">server.tomcat.accesslog.pattern</span>=<span class="string">%h %l %u %t "%r" %s %b %D %&#123;User-Agent&#125;i</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">nacos.security.ignore.urls</span>=<span class="string">/,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-fe/public/**,/v1/auth/login,/v1/console/health/**,/v1/cs/**,/v1/ns/**,/v1/cmdb/**,/actuator/**,/v1/console/server/**</span></span><br><span class="line"><span class="meta">nacos.naming.distro.taskDispatchThreadCount</span>=<span class="string">1</span></span><br><span class="line"><span class="meta">nacos.naming.distro.taskDispatchPeriod</span>=<span class="string">200</span></span><br><span class="line"><span class="meta">nacos.naming.distro.batchSyncKeyCount</span>=<span class="string">1000</span></span><br><span class="line"><span class="meta">nacos.naming.distro.initDataRatio</span>=<span class="string">0.9</span></span><br><span class="line"><span class="meta">nacos.naming.distro.syncRetryDelay</span>=<span class="string">5000</span></span><br><span class="line"><span class="meta">nacos.naming.data.warmup</span>=<span class="string">true</span></span><br><span class="line"><span class="meta">nacos.naming.expireInstance</span>=<span class="string">true</span></span><br></pre></td></tr></table></figure>

<p>:wq 保存退出</p>
<h6 id="3-启动容器"><a href="#3-启动容器" class="headerlink" title="3.启动容器"></a>3.启动容器</h6><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">docker  run \</span><br><span class="line">--name nacos -d \</span><br><span class="line">-p <span class="number">8848</span>:<span class="number">8848</span> \</span><br><span class="line">--privileged=<span class="literal">true</span> \</span><br><span class="line">--restart=always \</span><br><span class="line">-e JVM_XMS=<span class="number">256</span>m \</span><br><span class="line">-e JVM_XMX=<span class="number">256</span>m \</span><br><span class="line">-e MODE=standalone \</span><br><span class="line">-e PREFER_HOST_MODE=hostname \</span><br><span class="line">-v ~<span class="regexp">/data/</span>nacos/logs/:<span class="regexp">/home/</span>nacos/logs \</span><br><span class="line">-v ~<span class="regexp">/data/</span>nacos/init.d/application.properties:<span class="regexp">/home/</span>nacos/init.d/application.properties \</span><br><span class="line">nacos/nacos-server</span><br></pre></td></tr></table></figure>


      
    </div>
    <footer class="article-footer">
      <a data-url="https://bianchengme.gitee.io/2021/07/02/Nacos%E9%80%9A%E8%BF%87Docke%E9%83%A8%E7%BD%B2/" data-id="cktcaskha000z3guc343141vi" class="article-share-link">Share</a>
      
      
  <ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/Docker/" rel="tag">Docker</a></li><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/nacos/" rel="tag">nacos</a></li><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/%E9%83%A8%E7%BD%B2/" rel="tag">部署</a></li></ul>

    </footer>
  </div>
  
</article>


  
    <article id="post-Docker安装mysql5-7" class="article article-type-post" itemscope itemprop="blogPost">
  <div class="article-meta">
    <a href="/2021/07/01/Docker%E5%AE%89%E8%A3%85mysql5-7/" class="article-date">
  <time datetime="2021-07-01T06:22:32.000Z" itemprop="datePublished">2021-07-01</time>
</a>
    
  <div class="article-category">
    <a class="article-category-link" href="/categories/Docker/">Docker</a>►<a class="article-category-link" href="/categories/Docker/mysql/">mysql</a>
  </div>

  </div>
  <div class="article-inner">
    
    
      <header class="article-header">
        
  
    <h1 itemprop="name">
      <a class="article-title" href="/2021/07/01/Docker%E5%AE%89%E8%A3%85mysql5-7/">Docker安装mysql5.7</a>
    </h1>
  

      </header>
    
    <div class="article-entry" itemprop="articleBody">
      
        <h1 id="1、拉取镜像"><a href="#1、拉取镜像" class="headerlink" title="1、拉取镜像"></a>1、拉取镜像</h1><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">docker pull mysql:5.7</span><br><span class="line">docker images</span><br></pre></td></tr></table></figure>

<h1 id="2-创建-mysql-容器"><a href="#2-创建-mysql-容器" class="headerlink" title="2.创建 mysql 容器"></a>2.创建 mysql 容器</h1><h2 id="在本地创建-mysql-的映射目录"><a href="#在本地创建-mysql-的映射目录" class="headerlink" title="在本地创建 mysql 的映射目录"></a>在本地创建 mysql 的映射目录</h2><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">mkdir -p /home/ubuntu/data/mysql/mysql57/data /home/ubuntu/data/mysql/mysql57/logs /home/ubuntu/data/mysql/mysql57/conf</span><br><span class="line">cd /root/mysql/conf</span><br><span class="line">touch my.cnf  #修改配置再my.cnf中写即可</span><br></pre></td></tr></table></figure>

<h2 id="创建容器-将数据-日志-配置文件映射到本机"><a href="#创建容器-将数据-日志-配置文件映射到本机" class="headerlink" title="创建容器,将数据,日志,配置文件映射到本机"></a>创建容器,将数据,日志,配置文件映射到本机</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker run -p 3306:3306 --name mysql -v &#x2F;home&#x2F;ubuntu&#x2F;data&#x2F;mysql&#x2F;mysql57&#x2F;conf:&#x2F;etc&#x2F;mysql&#x2F;conf.d -v &#x2F;home&#x2F;ubuntu&#x2F;data&#x2F;mysql&#x2F;mysql57&#x2F;logs:&#x2F;logs -v &#x2F;home&#x2F;ubuntu&#x2F;data&#x2F;mysql&#x2F;mysql57&#x2F;data:&#x2F;var&#x2F;lib&#x2F;mysql -e MYSQL_ROOT_PASSWORD&#x3D;root -d mysql:5.7</span><br></pre></td></tr></table></figure>

<p>-d: 后台运行容器</p>
<p>-p 将容器的端口映射到本机的端口</p>
<p>-v 将主机目录挂载到容器的目录</p>
<p>-e 设置参数</p>

      
    </div>
    <footer class="article-footer">
      <a data-url="https://bianchengme.gitee.io/2021/07/01/Docker%E5%AE%89%E8%A3%85mysql5-7/" data-id="cktcaskgw00093guc65vgbfdu" class="article-share-link">Share</a>
      
      
  <ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/docker/" rel="tag">docker</a></li><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/mysl/" rel="tag">mysl</a></li></ul>

    </footer>
  </div>
  
</article>


  
    <article id="post-Docker安装redis" class="article article-type-post" itemscope itemprop="blogPost">
  <div class="article-meta">
    <a href="/2021/07/01/Docker%E5%AE%89%E8%A3%85redis/" class="article-date">
  <time datetime="2021-07-01T06:22:32.000Z" itemprop="datePublished">2021-07-01</time>
</a>
    
  <div class="article-category">
    <a class="article-category-link" href="/categories/docker/">docker</a>►<a class="article-category-link" href="/categories/docker/redis/">redis</a>
  </div>

  </div>
  <div class="article-inner">
    
    
      <header class="article-header">
        
  
    <h1 itemprop="name">
      <a class="article-title" href="/2021/07/01/Docker%E5%AE%89%E8%A3%85redis/">Docker安装redis</a>
    </h1>
  

      </header>
    
    <div class="article-entry" itemprop="articleBody">
      
        <h1 id="1、拉取镜像"><a href="#1、拉取镜像" class="headerlink" title="1、拉取镜像"></a>1、拉取镜像</h1><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker pull redis</span><br></pre></td></tr></table></figure>

<h1 id="2、创建容器"><a href="#2、创建容器" class="headerlink" title="2、创建容器"></a>2、创建容器</h1><h2 id="创建本地环境映射"><a href="#创建本地环境映射" class="headerlink" title="创建本地环境映射"></a>创建本地环境映射</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">mkdir -p ~&#x2F;data&#x2F;redis&#x2F;dada ~&#x2F;data&#x2F;redis&#x2F;etc&#x2F;</span><br><span class="line">cd ~&#x2F;data&#x2F;redis&#x2F;etc&#x2F;</span><br><span class="line">vi redis.conf</span><br><span class="line"></span><br><span class="line">##文件内内容</span><br><span class="line">daemonize no</span><br><span class="line">requirepass 123456</span><br><span class="line">appendonly yes</span><br><span class="line">tcp-keepalive 300</span><br></pre></td></tr></table></figure>

<h2 id="创建容器"><a href="#创建容器" class="headerlink" title="创建容器"></a>创建容器</h2><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo docker run -p 6379:6379 --name redis -v ~/data/redis/etc/redis.conf:/usr/local/etc/redis/redis.conf -v ~/data/redis/data:/data -d redis:6.2 redis-server /usr/local/etc/redis/redis.conf --appendonly yes</span><br></pre></td></tr></table></figure>

<blockquote>
<p><code>redis-server /usr/local/etc/redis/redis.conf --appendonly yes</code> 运行命令追加在最后</p>
</blockquote>
<p>-d, –detach=false， 指定容器运行于前台还是后台，默认为 false<br>-i, –interactive=false， 打开 STDIN，用于控制台交互<br>-t, –tty=false， 分配 tty 设备，该可以支持终端登录，默认为 false<br>-u, –user=””， 指定容器的用户<br>-a, –attach=[]， 登录容器（必须是以 docker run -d 启动的容器）<br>-w, –workdir=””， 指定容器的工作目录<br>-c, –cpu-shares=0， 设置容器 CPU 权重，在 CPU 共享场景使用<br>-e, –env=[]， 指定环境变量，容器中可以使用该环境变量<br>-m, –memory=””， 指定容器的内存上限<br>-P, –publish-all=false， 指定容器暴露的端口<br>-p, –publish=[]， 指定容器暴露的端口<br>-h, –hostname=””， 指定容器的主机名<br>-v, –volume=[]， 给容器挂载存储卷，挂载到容器的某个目录<br>–volumes-from=[]， 给容器挂载其他容器上的卷，挂载到容器的某个目录<br>–cap-add=[]， 添加权限，权限清单详见：<a href="http://linux.die.net/man/7/capabilities" target="_blank" rel="noopener">http://linux.die.net/man/7/capabilities</a><br>–cap-drop=[]， 删除权限，权限清单详见：<a href="http://linux.die.net/man/7/capabilities" target="_blank" rel="noopener">http://linux.die.net/man/7/capabilities</a><br>–cidfile=””， 运行容器后，在指定文件中写入容器 PID 值，一种典型的监控系统用法<br>–cpuset=””， 设置容器可以使用哪些 CPU，此参数可以用来容器独占 CPU<br>–device=[]， 添加主机设备给容器，相当于设备直通<br>–dns=[]， 指定容器的 dns 服务器<br>–dns-search=[]， 指定容器的 dns 搜索域名，写入到容器的/etc/resolv.conf 文件<br>–entrypoint=””， 覆盖 image 的入口点<br>–env-file=[]， 指定环境变量文件，文件格式为每行一个环境变量<br>–expose=[]， 指定容器暴露的端口，即修改镜像的暴露端口<br>–link=[]， 指定容器间的关联，使用其他容器的 IP、env 等信息<br>–lxc-conf=[]， 指定容器的配置文件，只有在指定–exec-driver=lxc 时使用<br>–name=””， 指定容器名字，后续可以通过名字进行容器管理，links 特性需要使用名字<br>–net=”bridge”， 容器网络设置:<br>bridge 使用 docker daemon 指定的网桥<br>host //容器使用主机的网络<br>container:NAME_or_ID &gt;//使用其他容器的网路，共享 IP 和 PORT 等网络资源<br>none 容器使用自己的网络（类似–net=bridge），但是不进行配置<br>–privileged=false， 指定容器是否为特权容器，特权容器拥有所有的 capabilities<br>–restart=”no”， 指定容器停止后的重启策略:<br>no：容器退出时不重启<br>on-failure：容器故障退出（返回值非零）时重启<br>always：容器退出时总是重启<br>–rm=false， 指定容器停止后自动删除容器(不支持以 docker run -d 启动的容器)<br>–sig-proxy=true， 设置由代理接受并处理信号，但是 SIGCHLD、SIGSTOP 和 SIGKILL 不能被代理</p>
<h2 id="进入容器"><a href="#进入容器" class="headerlink" title="进入容器"></a>进入容器</h2><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker exec -it redis /bin/bash</span><br></pre></td></tr></table></figure>

      
    </div>
    <footer class="article-footer">
      <a data-url="https://bianchengme.gitee.io/2021/07/01/Docker%E5%AE%89%E8%A3%85redis/" data-id="cktcaskgz000c3gucb7y4evas" class="article-share-link">Share</a>
      
      
  <ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/docker/" rel="tag">docker</a></li><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/redis/" rel="tag">redis</a></li></ul>

    </footer>
  </div>
  
</article>


  
    <article id="post-Java并发编程-volatile关键字理解" class="article article-type-post" itemscope itemprop="blogPost">
  <div class="article-meta">
    <a href="/2021/06/30/Java%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B-volatile%E5%85%B3%E9%94%AE%E5%AD%97%E7%90%86%E8%A7%A3/" class="article-date">
  <time datetime="2021-06-30T06:17:17.000Z" itemprop="datePublished">2021-06-30</time>
</a>
    
  <div class="article-category">
    <a class="article-category-link" href="/categories/java/">java</a>►<a class="article-category-link" href="/categories/java/%E5%A4%9A%E7%BA%BF%E7%A8%8B/">多线程</a>
  </div>

  </div>
  <div class="article-inner">
    
    
      <header class="article-header">
        
  
    <h1 itemprop="name">
      <a class="article-title" href="/2021/06/30/Java%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B-volatile%E5%85%B3%E9%94%AE%E5%AD%97%E7%90%86%E8%A7%A3/">Java并发编程-volatile关键字理解</a>
    </h1>
  

      </header>
    
    <div class="article-entry" itemprop="articleBody">
      
        <h1 id="背景与概念"><a href="#背景与概念" class="headerlink" title="背景与概念"></a>背景与概念</h1><p>理解volatile这个Java关键字之前，需要先了解几个关于内存模型与并发编程的概念：</p>
<h2 id="内存模型的相关概念"><a href="#内存模型的相关概念" class="headerlink" title="内存模型的相关概念"></a>内存模型的相关概念</h2><p>在计算机执行程序过程中，指令的执行有CPU完成；指令对应的数据的存储有物理内存完成；而cpu的执行速度要比物理内存的读写速度快很多；如果CPU每次都要直接和物理内存交互，就会大大降低CPU的执行效率。为了解决这一问题，在CPU中加入了高速缓存。</p>
<p>在程序执行过程中，会将运算需要的数据从物理内存复制一份到CPU的高度缓存中，CPU在计算时候直接读写高速缓存中的数据；当运算结束后，再将高速缓存的数据更新到物理内存中。例如：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">a = a + <span class="number">1</span>;</span><br></pre></td></tr></table></figure>

<p>程序执行过程中，会先从物理内存中读取a的值，并放到告诉缓存中，然后CPU执行指令从高速缓存中读取，并对a的值加1，然后写入高速缓存中，最后将高速缓存中的值刷新到物理内存中。</p>
<p>这种方式在单线程中没有任何问题，但是在多线程中就存在问题：在多核CPU中，多线程可能运行在多个CPU中，所有每个线程运行时都会有自己的高速缓存，（单核中多线程也存在相应问题，是以线程调度的形式来分别执行的，只不过多核更好理解）上面的例子在多核多线程中运行：</p>
<p>加入有两个线程，a的初始值是0在两个线程执行后，a的值我们期望的是2，事实如此吗？<br>在实际运行中可能存在：两个线程同时运行，分别从物理内存中读取a的初始值0到高速缓存中，经过CPU执行指令后，两个线程的高速缓存中的值都是1，然后又分别更新到物理内存中，结果就是最后物理内存中的值是1。这就是著名的缓存一致性问题。通常称这种被多个线程访问的变量为共享变量。</p>
<p>也就是说，如果一个变量在多个CPU中都存在缓存（一般在多线程编程时才会出现），那么就可能存在缓存不一致的问题。为了解决缓存不一致性问题，通常来说有以下2种解决方法：</p>
<p>　　- 通过在总线加LOCK#锁的方式</p>
<p>　　- 通过缓存一致性协议</p>
<p>这2种方式都是硬件层面上提供的方式。</p>
<p>在早期的CPU当中，是通过在总线上加LOCK#锁的形式来解决缓存不一致的问题。因为CPU和其他部件进行通信都是通过总线来进行的，如果对总线加LOCK#锁的话，也就是说阻塞了其他CPU对其他部件访问（如内存），从而使得只能有一个CPU能使用这个变量的内存。比如上面例子中 如果一个线程在执行 a = a +1，如果在执行这段代码的过程中，在总线上发出了LCOK#锁的信号，那么只有等待这段代码完全执行完毕之后，其他CPU才能从变量i所在的内存读取变量，然后进行相应的操作。这样就解决了缓存不一致的问题。</p>
<p>但是上面的方式会有一个问题，由于在锁住总线期间，其他CPU无法访问内存，导致效率低下。</p>
<p>所以就出现了缓存一致性协议。最出名的就是Intel 的MESI协议，MESI协议保证了每个缓存中使用的共享变量的副本是一致的。它核心的思想是：当CPU写数据时，如果发现操作的变量是共享变量，即在其他CPU中也存在该变量的副本，会发出信号通知其他CPU将该变量的缓存行置为无效状态，因此当其他CPU需要读取这个变量时，发现自己缓存中缓存该变量的缓存行是无效的，那么它就会从内存重新读取。</p>
<h2 id="并发编程的三个概念"><a href="#并发编程的三个概念" class="headerlink" title="并发编程的三个概念"></a>并发编程的三个概念</h2><p>使用多线程执行程序，经常面临并发编程的问题：原子性问题、可见性问题、有序性问题。</p>
<h3 id="原子性"><a href="#原子性" class="headerlink" title="原子性"></a>原子性</h3><p>即一个操作或者多个操作要么全部执行并且执行的过程不会被任何因素打断，要么就都不执行<br>举个最简单的例子，大家想一下假如为一个32位的变量赋值过程不具备原子性的话，会发生什么后果？</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">i &#x3D; 9;</span><br></pre></td></tr></table></figure>
<p>假若一个线程执行到这个语句时，我暂且假设为一个32位的变量赋值包括两个过程：为低16位赋值，为高16位赋值。<br>那么就可能发生一种情况：当将低16位数值写入之后，突然被中断，而此时又有一个线程去读取i的值，那么读取到的就是错误的数据。</p>
<h3 id="可见性"><a href="#可见性" class="headerlink" title="可见性"></a>可见性</h3><p>可见性是指当多个线程访问同一个变量时，一个线程修改了这个变量的值，其他线程能够立即看得到修改的值。<br>上文中的<code>a = a + 1;</code>在并发中，就是存在可见性问题；</p>
<h3 id="有序性"><a href="#有序性" class="headerlink" title="有序性"></a>有序性</h3><p>有序性：即程序执行的顺序按照代码的先后顺序执行。<br>令重排序，一般来说，处理器为了提高程序运行效率，可能会对输入代码进行优化，它不保证程序中各个语句的执行先后顺序同代码中的顺序一致，但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。<br>处理器在进行重排序时是会考虑指令之间的数据依赖性，所以对单个线程无影响，但是并发编程有影响：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//线程1:</span></span><br><span class="line">context = loadContext();   <span class="comment">//语句1</span></span><br><span class="line">inited = <span class="keyword">true</span>;             <span class="comment">//语句2</span></span><br><span class="line"> </span><br><span class="line"><span class="comment">//线程2:</span></span><br><span class="line"><span class="keyword">while</span>(!inited )&#123;</span><br><span class="line">  sleep()</span><br><span class="line">&#125;</span><br><span class="line">doSomethingwithconfig(context);</span><br></pre></td></tr></table></figure>

<p>上面代码中，由于语句1和语句2没有数据依赖性，因此可能会被重排序。假如发生了重排序，在线程1执行过程中先执行语句2，而此是线程2会以为初始化工作已经完成，那么就会跳出while循环，去执行doSomethingwithconfig(context)方法，而此时context并没有被初始化，就会导致程序出错。</p>
<p>也就是说，要想并发程序正确地执行，必须要保证原子性、可见性以及有序性。只要有一个没有被保证，就有可能会导致程序运行不正确。</p>
<h2 id="java内存模型"><a href="#java内存模型" class="headerlink" title="java内存模型"></a>java内存模型</h2><p>在Java虚拟机规范中试图定义一种Java内存模型（Java Memory Model，JMM）来屏蔽各个硬件平台和操作系统的内存访问差异，以实现让Java程序在各种平台下都能达到一致的内存访问效果。那么Java内存模型规定了哪些东西呢，它定义了程序中变量的访问规则，往大一点说是定义了程序执行的次序。注意，为了获得较好的执行性能，Java内存模型并没有限制执行引擎使用处理器的寄存器或者高速缓存来提升指令执行速度，也没有限制编译器对指令进行重排序。也就是说，在java内存模型中，也会存在原子性问题、缓存一致性问题和指令重排序的问题。</p>
<blockquote>
<p>Java内存模型规定所有的变量都是存在主存当中（类似于前面说的物理内存），每个线程都有自己的工作内存（类似于前面的高速缓存）。线程对变量的所有操作都必须在工作内存中进行，而不能直接对主存进行操作。并且每个线程不能访问其他线程的工作内存。</p>
</blockquote>
<h2 id="volatile关键字"><a href="#volatile关键字" class="headerlink" title="volatile关键字"></a>volatile关键字</h2><p>　一旦一个共享变量（类的成员变量、类的静态成员变量）被volatile修饰之后，那么就具备了在并发程序中两个特性：</p>
<ul>
<li>保证可见性；</li>
<li>禁止指令重排；<blockquote>
<p>注意并不能保证原子性；</p>
</blockquote>
</li>
</ul>
<h1 id="测试"><a href="#测试" class="headerlink" title="测试"></a>测试</h1><h2 id="可见性-1"><a href="#可见性-1" class="headerlink" title="可见性"></a>可见性</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//线程1</span></span><br><span class="line"><span class="keyword">boolean</span> stop = <span class="keyword">false</span>;</span><br><span class="line"><span class="keyword">while</span>(!stop)&#123;</span><br><span class="line">    doSomething();</span><br><span class="line">&#125;</span><br><span class="line"> </span><br><span class="line"><span class="comment">//线程2</span></span><br><span class="line">stop = <span class="keyword">true</span>;</span><br></pre></td></tr></table></figure>
<p>这段代码是很典型的一段代码，很多人在中断线程时可能都会采用这种标记办法。但是事实上，这段代码会完全运行正确么？即一定会将线程中断么？不一定，也许在大多数时候，这个代码能够把线程中断，但是也有可能会导致无法中断线程（虽然这个可能性很小，但是只要一旦发生这种情况就会造成死循环了）。</p>
<p>下面解释一下这段代码为何有可能导致无法中断线程。在前面已经解释过，每个线程在运行过程中都有自己的工作内存，那么线程1在运行的时候，会将stop变量的值拷贝一份放在自己的工作内存当中。</p>
<p>那么当线程2更改了stop变量的值之后，但是还没来得及写入主存当中，线程2转去做其他事情了，那么线程1由于不知道线程2对stop变量的更改，因此还会一直循环下去。</p>
<p>但是用volatile修饰之后就变得不一样了：</p>
<p>第一：使用volatile关键字会强制将修改的值立即写入主存；</p>
<p>第二：使用volatile关键字的话，当线程2进行修改时，会导致线程1的工作内存中缓存变量stop的缓存行无效（反映到硬件层的话，就是CPU的L1或者L2缓存中对应的缓存行无效）；</p>
<p>第三：由于线程1的工作内存中缓存变量stop的缓存行无效，所以线程1再次读取变量stop的值时会去主存读取。</p>
<p>那么在线程2修改stop值时（当然这里包括2个操作，修改线程2工作内存中的值，然后将修改后的值写入内存），会使得线程1的工作内存中缓存变量stop的缓存行无效，然后线程1读取时，发现自己的缓存行无效，它会等待缓存行对应的主存地址被更新之后，然后去对应的主存读取最新的值。</p>
<p>那么线程1读取到的就是最新的正确的值。</p>
<h2 id="指令重排"><a href="#指令重排" class="headerlink" title="指令重排"></a>指令重排</h2><p>在前面提到volatile关键字能禁止指令重排序，所以volatile能在一定程度上保证有序性。</p>
<p>volatile关键字禁止指令重排序有两层意思：</p>
<p>1）当程序执行到volatile变量的读操作或者写操作时，在其前面的操作的更改肯定全部已经进行，且结果已经对后面的操作可见；在其后面的操作肯定还没有进行；</p>
<p>2）在进行指令优化时，不能将在对volatile变量访问的语句放在其后面执行，也不能把volatile变量后面的语句放到其前面执行。</p>
<p>可能上面说的比较绕，举个简单的例子：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//x、y为非volatile变量</span></span><br><span class="line"><span class="comment">//flag为volatile变量</span></span><br><span class="line"> </span><br><span class="line">x = <span class="number">2</span>;        <span class="comment">//语句1</span></span><br><span class="line">y = <span class="number">0</span>;        <span class="comment">//语句2</span></span><br><span class="line">flag = <span class="keyword">true</span>;  <span class="comment">//语句3</span></span><br><span class="line">x = <span class="number">4</span>;         <span class="comment">//语句4</span></span><br><span class="line">y = -<span class="number">1</span>;       <span class="comment">//语句5</span></span><br></pre></td></tr></table></figure>
<p>由于flag变量为volatile变量，那么在进行指令重排序的过程的时候，不会将语句3放到语句1、语句2前面，也不会讲语句3放到语句4、语句5后面。但是要注意语句1和语句2的顺序、语句4和语句5的顺序是不作任何保证的。</p>
<p>并且volatile关键字能保证，执行到语句3时，语句1和语句2必定是执行完毕了的，且语句1和语句2的执行结果对语句3、语句4、语句5是可见的。</p>
<p>那么我们回到前面举的一个例子：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//线程1:</span></span><br><span class="line">context = loadContext();   <span class="comment">//语句1</span></span><br><span class="line">inited = <span class="keyword">true</span>;             <span class="comment">//语句2</span></span><br><span class="line"> </span><br><span class="line"><span class="comment">//线程2:</span></span><br><span class="line"><span class="keyword">while</span>(!inited )&#123;</span><br><span class="line">  sleep()</span><br><span class="line">&#125;</span><br><span class="line">doSomethingwithconfig(context);</span><br></pre></td></tr></table></figure>
<p>前面举这个例子的时候，提到有可能语句2会在语句1之前执行，那么久可能导致context还没被初始化，而线程2中就使用未初始化的context去进行操作，导致程序出错。</p>
<p>这里如果用volatile关键字对inited变量进行修饰，就不会出现这种问题了，因为当执行到语句2时，必定能保证context已经初始化完毕。</p>
<h2 id="原子性-1"><a href="#原子性-1" class="headerlink" title="原子性"></a>原子性</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Test</span> </span>&#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">volatile</span> <span class="keyword">int</span> inc = <span class="number">0</span>;</span><br><span class="line">     </span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">increase</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        inc++;</span><br><span class="line">    &#125;</span><br><span class="line">     </span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">final</span> Test test = <span class="keyword">new</span> Test();</span><br><span class="line">        <span class="keyword">for</span>(<span class="keyword">int</span> i=<span class="number">0</span>;i&lt;<span class="number">10</span>;i++)&#123;</span><br><span class="line">            <span class="keyword">new</span> Thread()&#123;</span><br><span class="line">                <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>&#123;</span><br><span class="line">                    <span class="keyword">for</span>(<span class="keyword">int</span> j=<span class="number">0</span>;j&lt;<span class="number">1000</span>;j++)</span><br><span class="line">                        test.increase();</span><br><span class="line">                &#125;;</span><br><span class="line">            &#125;.start();</span><br><span class="line">        &#125;</span><br><span class="line">         </span><br><span class="line">        <span class="keyword">while</span>(Thread.activeCount()&gt;<span class="number">1</span>)  <span class="comment">//保证前面的线程都执行完</span></span><br><span class="line">            Thread.yield();</span><br><span class="line">        System.out.println(test.inc);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>大家想一下这段程序的输出结果是多少？也许有些朋友认为是10000。但是事实上运行它会发现每次运行结果都不一致，都是一个小于10000的数字。</p>
<p>可能有的朋友就会有疑问，不对啊，上面是对变量inc进行自增操作，由于volatile保证了可见性，那么在每个线程中对inc自增完之后，在其他线程中都能看到修改后的值啊，所以有10个线程分别进行了1000次操作，那么最终inc的值应该是1000*10=10000。</p>
<p>这里面就有一个误区了，volatile关键字能保证可见性没有错，但是上面的程序错在没能保证原子性。可见性只能保证每次读取的是最新的值，但是volatile没办法保证对变量的操作的原子性。</p>
<p>在前面已经提到过，自增操作是不具备原子性的，它包括读取变量的原始值、进行加1操作、写入工作内存。那么就是说自增操作的三个子操作可能会分割开执行，就有可能导致下面这种情况出现：</p>
<p>假如某个时刻变量inc的值为10，</p>
<p>线程1对变量进行自增操作，线程1先读取了变量inc的原始值，然后线程1被阻塞了；</p>
<p>然后线程2对变量进行自增操作，线程2也去读取变量inc的原始值，由于线程1只是对变量inc进行读取操作，而没有对变量进行修改操作，所以不会导致线程2的工作内存中缓存变量inc的缓存行无效，所以线程2会直接去主存读取inc的值，发现inc的值时10，然后进行加1操作，并把11写入工作内存，最后写入主存。</p>
<p>然后线程1接着进行加1操作，由于已经读取了inc的值，注意此时在线程1的工作内存中inc的值仍然为10，所以线程1对inc进行加1操作后inc的值为11，然后将11写入工作内存，最后写入主存。</p>
<p>那么两个线程分别进行了一次自增操作后，inc只增加了1。</p>
<p>解释到这里，可能有朋友会有疑问，不对啊，前面不是保证一个变量在修改volatile变量时，会让缓存行无效吗？然后其他线程去读就会读到新的值，对，这个没错。这个就是上面的happens-before规则中的volatile变量规则，但是要注意，线程1对变量进行读取操作之后，被阻塞了的话，并没有对inc值进行修改。然后虽然volatile能保证线程2对变量inc的值读取是从内存中读取的，但是线程1没有进行修改，所以线程2根本就不会看到修改的值。</p>
<p>根源就在这里，自增操作不是原子性操作，而且volatile也无法保证对变量的任何操作都是原子性的。</p>
<p>实际中解决原子性问题方法有多种：</p>
<p>采用synchronized：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Test</span> </span>&#123;</span><br><span class="line">    <span class="keyword">public</span>  <span class="keyword">int</span> inc = <span class="number">0</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">synchronized</span> <span class="keyword">void</span> <span class="title">increase</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        inc++;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">final</span> Test test = <span class="keyword">new</span> Test();</span><br><span class="line">        <span class="keyword">for</span>(<span class="keyword">int</span> i=<span class="number">0</span>;i&lt;<span class="number">10</span>;i++)&#123;</span><br><span class="line">            <span class="keyword">new</span> Thread()&#123;</span><br><span class="line">                <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>&#123;</span><br><span class="line">                    <span class="keyword">for</span>(<span class="keyword">int</span> j=<span class="number">0</span>;j&lt;<span class="number">1000</span>;j++)</span><br><span class="line">                        test.increase();</span><br><span class="line">                &#125;;</span><br><span class="line">            &#125;.start();</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">while</span>(Thread.activeCount()&gt;<span class="number">1</span>)  <span class="comment">//保证前面的线程都执行完</span></span><br><span class="line">            Thread.yield();</span><br><span class="line">        System.out.println(test.inc);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>采用Lock：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Test</span> </span>&#123;</span><br><span class="line">    <span class="keyword">public</span>  <span class="keyword">int</span> inc = <span class="number">0</span>;</span><br><span class="line">    Lock lock = <span class="keyword">new</span> ReentrantLock();</span><br><span class="line">    </span><br><span class="line">    <span class="function"><span class="keyword">public</span>  <span class="keyword">void</span> <span class="title">increase</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        lock.lock();</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            inc++;</span><br><span class="line">        &#125; <span class="keyword">finally</span>&#123;</span><br><span class="line">            lock.unlock();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">final</span> Test test = <span class="keyword">new</span> Test();</span><br><span class="line">        <span class="keyword">for</span>(<span class="keyword">int</span> i=<span class="number">0</span>;i&lt;<span class="number">10</span>;i++)&#123;</span><br><span class="line">            <span class="keyword">new</span> Thread()&#123;</span><br><span class="line">                <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>&#123;</span><br><span class="line">                    <span class="keyword">for</span>(<span class="keyword">int</span> j=<span class="number">0</span>;j&lt;<span class="number">1000</span>;j++)</span><br><span class="line">                        test.increase();</span><br><span class="line">                &#125;;</span><br><span class="line">            &#125;.start();</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">while</span>(Thread.activeCount()&gt;<span class="number">1</span>)  <span class="comment">//保证前面的线程都执行完</span></span><br><span class="line">            Thread.yield();</span><br><span class="line">        System.out.println(test.inc);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>采用AtomicInteger：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Test</span> </span>&#123;</span><br><span class="line">    <span class="keyword">public</span>  AtomicInteger inc = <span class="keyword">new</span> AtomicInteger();</span><br><span class="line">     </span><br><span class="line">    <span class="function"><span class="keyword">public</span>  <span class="keyword">void</span> <span class="title">increase</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        inc.getAndIncrement();</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">final</span> Test test = <span class="keyword">new</span> Test();</span><br><span class="line">        <span class="keyword">for</span>(<span class="keyword">int</span> i=<span class="number">0</span>;i&lt;<span class="number">10</span>;i++)&#123;</span><br><span class="line">            <span class="keyword">new</span> Thread()&#123;</span><br><span class="line">                <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>&#123;</span><br><span class="line">                    <span class="keyword">for</span>(<span class="keyword">int</span> j=<span class="number">0</span>;j&lt;<span class="number">1000</span>;j++)</span><br><span class="line">                        test.increase();</span><br><span class="line">                &#125;;</span><br><span class="line">            &#125;.start();</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">while</span>(Thread.activeCount()&gt;<span class="number">1</span>)  <span class="comment">//保证前面的线程都执行完</span></span><br><span class="line">            Thread.yield();</span><br><span class="line">        System.out.println(test.inc);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>在java 1.5的java.util.concurrent.atomic包下提供了一些原子操作类，即对基本数据类型的 自增（加1操作），自减（减1操作）、以及加法操作（加一个数），减法操作（减一个数）进行了封装，保证这些操作是原子性操作。atomic是利用CAS来实现原子性操作的（Compare And Swap），CAS实际上是利用处理器提供的CMPXCHG指令实现的，而处理器执行CMPXCHG指令是一个原子性操作。</p>
<h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>synchronized关键字是防止多个线程同时执行一段代码，那么就会很影响程序执行效率，而volatile关键字在某些情况下性能要优于synchronized，但是要注意volatile关键字是无法替代synchronized关键字的，因为volatile关键字无法保证操作的原子性。通常来说，使用volatile必须具备以下2个条件：</p>
<p>1）对变量的写操作不依赖于当前值</p>
<p>2）该变量没有包含在具有其他变量的不变式中</p>
<p>实际上，这些条件表明，可以被写入 volatile 变量的这些有效值独立于任何程序的状态，包括变量的当前状态。</p>
<p>事实上，我的理解就是上面的2个条件需要保证操作是原子性操作，才能保证使用volatile关键字的程序在并发时能够正确执行。</p>
<p>下面列举几个Java中使用volatile的几个场景。</p>
<p>1.状态标记量</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">volatile</span> <span class="keyword">boolean</span> flag = <span class="keyword">false</span>;</span><br><span class="line"> </span><br><span class="line"><span class="keyword">while</span>(!flag)&#123;</span><br><span class="line">    doSomething();</span><br><span class="line">&#125;</span><br><span class="line"> </span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setFlag</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    flag = <span class="keyword">true</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">volatile</span> <span class="keyword">boolean</span> inited = <span class="keyword">false</span>;</span><br><span class="line"><span class="comment">//线程1:</span></span><br><span class="line">context = loadContext();  </span><br><span class="line">inited = <span class="keyword">true</span>;            </span><br><span class="line"> </span><br><span class="line"><span class="comment">//线程2:</span></span><br><span class="line"><span class="keyword">while</span>(!inited )&#123;</span><br><span class="line">sleep()</span><br><span class="line">&#125;</span><br><span class="line">doSomethingwithconfig(context);</span><br></pre></td></tr></table></figure>
<p>2.double check(双端监测),线程安全的高效单例：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Singleton</span></span>&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">volatile</span> <span class="keyword">static</span> Singleton instance = <span class="keyword">null</span>;</span><br><span class="line">     </span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="title">Singleton</span><span class="params">()</span> </span>&#123;</span><br><span class="line">         </span><br><span class="line">    &#125;</span><br><span class="line">     </span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> Singleton <span class="title">getInstance</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span>(instance==<span class="keyword">null</span>) &#123;</span><br><span class="line">            <span class="keyword">synchronized</span> (Singleton<span class="class">.<span class="keyword">class</span>) </span>&#123;</span><br><span class="line">                <span class="keyword">if</span>(instance==<span class="keyword">null</span>)</span><br><span class="line">                    instance = <span class="keyword">new</span> Singleton();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> instance;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
      
    </div>
    <footer class="article-footer">
      <a data-url="https://bianchengme.gitee.io/2021/06/30/Java%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B-volatile%E5%85%B3%E9%94%AE%E5%AD%97%E7%90%86%E8%A7%A3/" data-id="cktcaskh2000e3guccrli7u50" class="article-share-link">Share</a>
      
      
  <ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/java/" rel="tag">java</a></li><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/volatile/" rel="tag">volatile</a></li><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/" rel="tag">并发编程</a></li></ul>

    </footer>
  </div>
  
</article>


  
    <article id="post-centos7-安装mysql5-7" class="article article-type-post" itemscope itemprop="blogPost">
  <div class="article-meta">
    <a href="/2021/05/09/centos7-%E5%AE%89%E8%A3%85mysql5-7/" class="article-date">
  <time datetime="2021-05-09T12:12:54.000Z" itemprop="datePublished">2021-05-09</time>
</a>
    
  <div class="article-category">
    <a class="article-category-link" href="/categories/mysql/">mysql</a>
  </div>

  </div>
  <div class="article-inner">
    
    
      <header class="article-header">
        
  
    <h1 itemprop="name">
      <a class="article-title" href="/2021/05/09/centos7-%E5%AE%89%E8%A3%85mysql5-7/">centos7 安装mysql5.7</a>
    </h1>
  

      </header>
    
    <div class="article-entry" itemprop="articleBody">
      
        <ol>
<li>创建用户和组</li>
</ol>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">groupadd mysql </span><br><span class="line">useradd -g mysql -s &#x2F;sbin&#x2F;nologin mysql</span><br></pre></td></tr></table></figure>


<ol start="2">
<li><p>解压到指定目录并重命名</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">#dev.mysql.com 下载 linux 通用二进制包</span><br><span class="line">tar xzvf XXX.tar.gz &#x2F;usr&#x2F;local&#x2F;mysql</span><br></pre></td></tr></table></figure>
</li>
<li><p>配置环境变量</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">echo &quot;export PATH&#x3D;$PATH:&#x2F;usr&#x2F;local&#x2F;mysql&#x2F;bin&quot; &gt;&gt; &#x2F;etc&#x2F;profile</span><br><span class="line"></span><br><span class="line">source &#x2F;etc&#x2F;profile</span><br></pre></td></tr></table></figure></li>
<li><p>创建数据目录，并配置权限</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">mkdir -p &#x2F;data&#x2F;mysql&#x2F;&#123;data,binlogs,log,etc,run&#125;</span><br><span class="line"></span><br><span class="line">ln -s &#x2F;data&#x2F;mysql&#x2F;data  &#x2F;usr&#x2F;local&#x2F;mysql&#x2F;data</span><br><span class="line"></span><br><span class="line">ln -s &#x2F;data&#x2F;mysql&#x2F;binlogs  &#x2F;usr&#x2F;local&#x2F;mysql&#x2F;binlogs</span><br><span class="line"></span><br><span class="line">ln -s &#x2F;data&#x2F;mysql&#x2F;log  &#x2F;usr&#x2F;local&#x2F;mysql&#x2F;log</span><br><span class="line"></span><br><span class="line">ln -s &#x2F;data&#x2F;mysql&#x2F;etc  &#x2F;usr&#x2F;local&#x2F;mysql&#x2F;etc</span><br><span class="line"></span><br><span class="line">ln -s &#x2F;data&#x2F;mysql&#x2F;run  &#x2F;usr&#x2F;local&#x2F;mysql&#x2F;run</span><br><span class="line"></span><br><span class="line">chown -R mysql.mysql   &#x2F;data&#x2F;mysql&#x2F;  # 设置文件夹为第一步创建的用户组、用户的权限</span><br><span class="line"></span><br><span class="line">chown -R mysql.mysql   &#x2F;usr&#x2F;local&#x2F;mysql&#x2F;&#123;data,binlogs,log,etc,run&#125; # 设置文件夹为第一步创建的用户组、用户</span><br></pre></td></tr></table></figure>
</li>
<li><p>删除系统自带的my.cnf</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">rm -f &#x2F;etc&#x2F;my.cnf</span><br></pre></td></tr></table></figure>
</li>
<li><p>在/usr/local/mysql/etc/下创建my.cnf文件，加入如下参数</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line">[client]</span><br><span class="line">port &#x3D; 3306</span><br><span class="line">socket &#x3D; &#x2F;usr&#x2F;local&#x2F;mysql&#x2F;run&#x2F;mysql.sock</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">[mysqld]</span><br><span class="line">pid_file &#x3D; &#x2F;usr&#x2F;local&#x2F;mysql&#x2F;run&#x2F;mysql.pid</span><br><span class="line">datadir &#x3D; &#x2F;usr&#x2F;local&#x2F;mysql&#x2F;data</span><br><span class="line">socket &#x3D; &#x2F;usr&#x2F;local&#x2F;mysql&#x2F;run&#x2F;mysql.sock</span><br><span class="line">max_connections &#x3D; 2048</span><br><span class="line">open_files_limit &#x3D; 65535</span><br><span class="line">skip-name-resolve</span><br><span class="line">lower_case_table_names&#x3D;1</span><br><span class="line">character-set-server &#x3D; utf8mb4</span><br><span class="line">collation-server &#x3D; utf8mb4_unicode_ci</span><br><span class="line">init_connect&#x3D;&#39;SET NAMES utf8mb4&#39;</span><br><span class="line">innodb_buffer_pool_size &#x3D; 1024M</span><br><span class="line">innodb_log_file_size &#x3D; 2048M</span><br><span class="line">innodb_file_per_table &#x3D; 1</span><br><span class="line">innodb_flush_log_at_trx_commit &#x3D; 0</span><br><span class="line">key_buffer_size &#x3D; 64M</span><br><span class="line">log-error &#x3D; &#x2F;usr&#x2F;local&#x2F;mysql&#x2F;log&#x2F;mysql_error.log</span><br><span class="line">log-bin &#x3D; &#x2F;usr&#x2F;local&#x2F;mysql&#x2F;binlogs&#x2F;mysql-bin</span><br><span class="line">slow_query_log &#x3D; 1</span><br><span class="line">slow_query_log_file &#x3D; &#x2F;usr&#x2F;local&#x2F;mysql&#x2F;log&#x2F;mysql_slow_query.log</span><br><span class="line">long_query_time &#x3D; 5   </span><br><span class="line">tmp_table_size &#x3D; 32M</span><br><span class="line">max_heap_table_size &#x3D; 32M</span><br><span class="line">query_cache_type &#x3D; 0</span><br><span class="line">query_cache_size &#x3D; 0</span><br><span class="line">server-id&#x3D;1</span><br></pre></td></tr></table></figure>
</li>
<li><p>初始化mysql</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mysqld --initialize --user&#x3D;mysql --basedir&#x3D;&#x2F;usr&#x2F;local&#x2F;mysql --datadir&#x3D;&#x2F;usr&#x2F;local&#x2F;mysql&#x2F;data</span><br></pre></td></tr></table></figure>
</li>
<li><p>记住生成的root密码</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">grep &#39;temporary password&#39; &#x2F;usr&#x2F;local&#x2F;mysql&#x2F;log&#x2F;mysql_error.log</span><br></pre></td></tr></table></figure>


</li>
</ol>
<ol start="9">
<li><p>生成ssl</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mysql_ssl_rsa_setup --basedir&#x3D;&#x2F;usr&#x2F;local&#x2F;mysql --datadir&#x3D;&#x2F;usr&#x2F;local&#x2F;mysql&#x2F;data&#x2F;</span><br></pre></td></tr></table></figure>
</li>
<li><p>配置系统服务</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line">cd &#x2F;usr&#x2F;lib&#x2F;systemd&#x2F;system </span><br><span class="line">touch mysqld.service #并加入以下内容</span><br><span class="line"></span><br><span class="line">[Unit]</span><br><span class="line">Description&#x3D;MySQL Server</span><br><span class="line">Documentation&#x3D;man:mysqld(8)</span><br><span class="line">Documentation&#x3D;http:&#x2F;&#x2F;dev.mysql.com&#x2F;doc&#x2F;refman&#x2F;en&#x2F;using-systemd.html</span><br><span class="line">After&#x3D;network.target</span><br><span class="line">After&#x3D;syslog.target</span><br><span class="line"></span><br><span class="line">[Install]</span><br><span class="line">WantedBy&#x3D;multi-user.target</span><br><span class="line"></span><br><span class="line">[Service]</span><br><span class="line">User&#x3D;mysql</span><br><span class="line">Group&#x3D;mysql</span><br><span class="line">Type&#x3D;forking</span><br><span class="line">PIDFile&#x3D;&#x2F;usr&#x2F;local&#x2F;mysql&#x2F;run&#x2F;mysqld.pid    </span><br><span class="line">\# Disable service start and stop timeout logic of systemd for mysqld service.</span><br><span class="line">TimeoutSec&#x3D;0</span><br><span class="line">\# Execute pre and post scripts as root</span><br><span class="line">PermissionsStartOnly&#x3D;true</span><br><span class="line">\# Needed to create system tables</span><br><span class="line">\#ExecStartPre&#x3D;&#x2F;usr&#x2F;bin&#x2F;mysqld_pre_systemd</span><br><span class="line">\# Start main service</span><br><span class="line">ExecStart&#x3D;&#x2F;usr&#x2F;local&#x2F;mysql&#x2F;bin&#x2F;mysqld --daemonize --pid-file&#x3D;&#x2F;usr&#x2F;local&#x2F;mysql&#x2F;run&#x2F;mysqld.pid $MYSQLD_OPTS</span><br><span class="line">\# Use this to switch malloc implementation</span><br><span class="line">EnvironmentFile&#x3D;-&#x2F;etc&#x2F;sysconfig&#x2F;mysql</span><br><span class="line">\# Sets open_files_limit</span><br><span class="line">LimitNOFILE &#x3D; 65535    </span><br><span class="line">Restart&#x3D;on-failure    </span><br><span class="line">RestartPreventExitStatus&#x3D;1    </span><br><span class="line">PrivateTmp&#x3D;false</span><br></pre></td></tr></table></figure>
</li>
<li><p>启动mysql服务以及开机自启动</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">systemctl enable mysqld</span><br><span class="line">systemctl start mysqld</span><br></pre></td></tr></table></figure>
</li>
<li><p>用步骤8中的root密码进行登录，第一次登陆需要设置密码</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">alter user &#39;root&#39;@&#39;localhost&#39; identified by &#39;root&#39; # 只能通过该方法修改；</span><br></pre></td></tr></table></figure>
</li>
<li><p>过程中遇到错误</p>
</li>
</ol>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ERROR 2002 (HY000): Can&#39;t connect to local MySQL server through socket &#39;&#x2F;usr&#x2F;local&#x2F;mysql&#x2F;run&#x2F;mysql.sock&#39; (2)</span><br></pre></td></tr></table></figure>

<p>分析：在/usr/local/mysql/run/mysql.sock 位置没有找到mysql.sock；通过命令 <code>find / -name mysqld.sock</code> , 发现mysql。sock 位置不对，不是my.cnf配置位置，shy?  </p>
<p>一番尝试发现 my.cnf,可以发现问题源头,不光[client] 中配置socket地址，[mysqld]中也要配置socket地址（上文已经更正错误），还是没有理解配置。。。</p>

      
    </div>
    <footer class="article-footer">
      <a data-url="https://bianchengme.gitee.io/2021/05/09/centos7-%E5%AE%89%E8%A3%85mysql5-7/" data-id="cktcaskhk001t3guc9k60f6zo" class="article-share-link">Share</a>
      
      
  <ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/mysql/" rel="tag">mysql</a></li></ul>

    </footer>
  </div>
  
</article>


  
    <article id="post-openstack-horizon-快速本地运行" class="article article-type-post" itemscope itemprop="blogPost">
  <div class="article-meta">
    <a href="/2021/03/12/openstack-horizon-%E5%BF%AB%E9%80%9F%E6%9C%AC%E5%9C%B0%E8%BF%90%E8%A1%8C/" class="article-date">
  <time datetime="2021-03-12T08:31:55.000Z" itemprop="datePublished">2021-03-12</time>
</a>
    
  </div>
  <div class="article-inner">
    
    
      <header class="article-header">
        
  
    <h1 itemprop="name">
      <a class="article-title" href="/2021/03/12/openstack-horizon-%E5%BF%AB%E9%80%9F%E6%9C%AC%E5%9C%B0%E8%BF%90%E8%A1%8C/">openstack-horizon 快速本地运行</a>
    </h1>
  

      </header>
    
    <div class="article-entry" itemprop="articleBody">
      
        <h2 id="horizon-简介"><a href="#horizon-简介" class="headerlink" title="horizon 简介"></a>horizon 简介</h2><p>Horizon 为 Openstack 提供一个 WEB 前端的管理界面 (UI 服务 )通过 Horizon 所提供的 DashBoard 服务 , 管理员可以使用通过 WEB UI 对 Openstack 整体云环境进行管理 , 并可直观看到各种操作结果与运行状态。那作为开发者怎样快速上手horizon？其实很简单，so easy！</p>
<h2 id="horizon本地运行"><a href="#horizon本地运行" class="headerlink" title="horizon本地运行"></a>horizon本地运行</h2><h3 id="1、准备本地环境"><a href="#1、准备本地环境" class="headerlink" title="1、准备本地环境"></a>1、准备本地环境</h3><p>安装python，不会自己百度搜吧 。。。。</p>
<h3 id="2、创建虚拟环境，推荐用virtualenvwrapper；"><a href="#2、创建虚拟环境，推荐用virtualenvwrapper；" class="headerlink" title="2、创建虚拟环境，推荐用virtualenvwrapper；"></a>2、创建虚拟环境，推荐用virtualenvwrapper；</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;&#x2F; 安装</span><br><span class="line">pip install virtualenvwrapper-win</span><br><span class="line"></span><br><span class="line">&#x2F;**</span><br><span class="line">* 默认创建的虚拟环境位于C:\Users\username\envs,可以通过环境变量 WORKON_HOME来定制。</span><br><span class="line">* 通过计算机--&gt;属性--&gt;高级系统设置--&gt;环境变量--&gt;在系统变量中新建“变量名”：WORKON_HOME,变量值：“你自定义的路径”。</span><br><span class="line">*&#x2F;</span><br><span class="line"></span><br><span class="line">&#x2F;&#x2F; 执行此命令创建第一个虚拟环境:</span><br><span class="line">mkvirtualenv venv</span><br><span class="line"></span><br><span class="line">&#x2F;&#x2F; 列出所有虚拟环境</span><br><span class="line">lsvirtualenv</span><br><span class="line"></span><br><span class="line">&#x2F;&#x2F; 激活虚拟环境</span><br><span class="line">workon venv</span><br><span class="line"></span><br><span class="line">&#x2F;&#x2F; 进入虚拟环境目录</span><br><span class="line">cdvirtualenv</span><br><span class="line"></span><br><span class="line">&#x2F;&#x2F; 进入虚拟环境的site-packages目录</span><br><span class="line">cdsitepackages</span><br><span class="line"></span><br><span class="line">&#x2F;&#x2F; 列出site-packages目录的所有软件包</span><br><span class="line">lssitepackages</span><br><span class="line"></span><br><span class="line">&#x2F;&#x2F; 停止虚拟环境</span><br><span class="line">deactivate</span><br><span class="line"></span><br><span class="line">&#x2F;&#x2F; 删除虚拟环境</span><br><span class="line">rmvitualenv venv</span><br></pre></td></tr></table></figure>



<h3 id="3、-获取项目代码"><a href="#3、-获取项目代码" class="headerlink" title="3、 获取项目代码"></a>3、 获取项目代码</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"># github clone 地址</span><br><span class="line">https:&#x2F;&#x2F;github.com&#x2F;openstack&#x2F;horizon.git</span><br></pre></td></tr></table></figure>

<h3 id="4、安装依赖"><a href="#4、安装依赖" class="headerlink" title="4、安装依赖"></a>4、安装依赖</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;&#x2F;项目目录下:</span><br><span class="line">pip install -r requirements.txt</span><br></pre></td></tr></table></figure>





<h3 id="5、配置项目"><a href="#5、配置项目" class="headerlink" title="5、配置项目"></a>5、配置项目</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;&#x2F; 创建配置文件</span><br><span class="line">cp openstack_dashboard&#x2F;local&#x2F;local_settings.py.example openstack_dashboard&#x2F;local&#x2F;local_settings.py</span><br></pre></td></tr></table></figure>



<h3 id="6、运行项目"><a href="#6、运行项目" class="headerlink" title="6、运行项目"></a>6、运行项目</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">python manage.py runserver</span><br><span class="line"></span><br><span class="line">&#x2F;&#x2F;访问localhost:8000 打开登录界面；OK</span><br></pre></td></tr></table></figure>



<h3 id="7、过程中遇到的问题"><a href="#7、过程中遇到的问题" class="headerlink" title="7、过程中遇到的问题"></a>7、过程中遇到的问题</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">Exception has occurred: FilePermissionError</span><br><span class="line">Insecure permissions on key file &#x2F;mnt&#x2F;f&#x2F;work&#x2F;openstack&#x2F;horizon&#x2F;openstack_dashboard&#x2F;local&#x2F;.secret_key_store, should be 0600</span><br><span class="line"></span><br><span class="line">&#x2F;&#x2F; 可能是linux文件权限错误，搜索后发现，local_settings.py使用了该文件，修改权限后依然不行，可能是我用了wsl的原因吧。</span><br><span class="line">&#x2F;&#x2F; 暂时先屏蔽 horizon&#x2F;openstack_dashboard&#x2F;local&#x2F;local_settings.py中对.secret_key_store的使用</span><br><span class="line"># SECRET_KEY &#x3D; secret_key.generate_or_read_from_file(os.path.join(LOCAL_PATH, &#39;.secret_key_store&#39;))</span><br><span class="line">SECRET_KEY&#x3D;&#39;GUM4T1AwQbF536JpKNQk10Vq0EpOIIudUQ0hpoAPBdTvDkgvUuuuGagAE4xajUVx&#39;</span><br></pre></td></tr></table></figure>


      
    </div>
    <footer class="article-footer">
      <a data-url="https://bianchengme.gitee.io/2021/03/12/openstack-horizon-%E5%BF%AB%E9%80%9F%E6%9C%AC%E5%9C%B0%E8%BF%90%E8%A1%8C/" data-id="cktcaskhw003a3guceo1ghum6" class="article-share-link">Share</a>
      
      
    </footer>
  </div>
  
</article>


  
    <article id="post-用npm安装sass教程和常见错误" class="article article-type-post" itemscope itemprop="blogPost">
  <div class="article-meta">
    <a href="/2020/10/11/%E7%94%A8npm%E5%AE%89%E8%A3%85sass%E6%95%99%E7%A8%8B%E5%92%8C%E5%B8%B8%E8%A7%81%E9%94%99%E8%AF%AF/" class="article-date">
  <time datetime="2020-10-11T08:41:39.000Z" itemprop="datePublished">2020-10-11</time>
</a>
    
  </div>
  <div class="article-inner">
    
    
      <header class="article-header">
        
  
    <h1 itemprop="name">
      <a class="article-title" href="/2020/10/11/%E7%94%A8npm%E5%AE%89%E8%A3%85sass%E6%95%99%E7%A8%8B%E5%92%8C%E5%B8%B8%E8%A7%81%E9%94%99%E8%AF%AF/">用npm安装sass教程和常见错误</a>
    </h1>
  

      </header>
    
    <div class="article-entry" itemprop="articleBody">
      
        <blockquote>
<p>用npm安装sass经常出错，原因是安装包比较大，并且源在国外，所以网络不好情况下容易安装失败；另外还依赖python环境，缺乏python环境也会安装失败。</p>
</blockquote>
<p>为保障安装成功，做一些配置即可安装成功：</p>
<p>方法一：使用淘宝源, 这样使用 npm install 安装 node-sass、electron 和 phantomjs 时都能自动从淘宝源上下载。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">npm config set sass_binary_site&#x3D;https:&#x2F;&#x2F;npm.taobao.org&#x2F;mirrors&#x2F;node-sass&#x2F;</span><br><span class="line">npm config set phantomjs_cdnurl&#x3D;https:&#x2F;&#x2F;npm.taobao.org&#x2F;mirrors&#x2F;phantomjs&#x2F;</span><br><span class="line">npm config set electron_mirror&#x3D;https:&#x2F;&#x2F;npm.taobao.org&#x2F;mirrors&#x2F;electron&#x2F;</span><br><span class="line">npm config set registry&#x3D;https:&#x2F;&#x2F;registry.npm.taobao.org</span><br></pre></td></tr></table></figure>

<p>方法二：安装cnpm &gt;&gt;&gt;  也是淘宝源的做法</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">npm install -g cnpm</span><br><span class="line">cnpm install</span><br></pre></td></tr></table></figure>

<p>方法三：使用VPN</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">npm config set proxy (http:&#x2F;&#x2F;127.0.0.1:1080) #此处是VPN的代理地址</span><br><span class="line">npm i node-sass</span><br><span class="line"># 下载完成后删除 http 代理</span><br><span class="line">npm config delete proxy</span><br></pre></td></tr></table></figure>



<blockquote>
<p>在安装过程中注意查看全部报错信息，比如会提示找不到 Python、找不到某个版本等等错误。按照问题分析解决即可：</p>
</blockquote>
<h4 id="提示没有Python环境"><a href="#提示没有Python环境" class="headerlink" title="提示没有Python环境"></a>提示没有Python环境</h4><p>这个很好解决，安装Pyhon，并确保配置好环境变量即可。</p>
<h4 id="提示找不到某个版本"><a href="#提示找不到某个版本" class="headerlink" title="提示找不到某个版本"></a>提示找不到某个版本</h4><p>安装的时候如果指定了某个版本，可能淘宝源上没有指定要的版本，访问<a href="https://npm.taobao.org/mirrors/node-sass/" target="_blank" rel="noopener">https://npm.taobao.org/mirrors/node-sass/</a> 看存在哪些版本切换为相近版本即可。</p>

      
    </div>
    <footer class="article-footer">
      <a data-url="https://bianchengme.gitee.io/2020/10/11/%E7%94%A8npm%E5%AE%89%E8%A3%85sass%E6%95%99%E7%A8%8B%E5%92%8C%E5%B8%B8%E8%A7%81%E9%94%99%E8%AF%AF/" data-id="cktcaskiu005r3gucerq3d7ry" class="article-share-link">Share</a>
      
      
    </footer>
  </div>
  
</article>


  
    <article id="post-项目基线目录-方便项目管理与交接" class="article article-type-post" itemscope itemprop="blogPost">
  <div class="article-meta">
    <a href="/2020/10/11/%E9%A1%B9%E7%9B%AE%E5%9F%BA%E7%BA%BF%E7%9B%AE%E5%BD%95-%E6%96%B9%E4%BE%BF%E9%A1%B9%E7%9B%AE%E7%AE%A1%E7%90%86%E4%B8%8E%E4%BA%A4%E6%8E%A5/" class="article-date">
  <time datetime="2020-10-11T06:57:25.000Z" itemprop="datePublished">2020-10-11</time>
</a>
    
  </div>
  <div class="article-inner">
    
    
      <header class="article-header">
        
  
    <h1 itemprop="name">
      <a class="article-title" href="/2020/10/11/%E9%A1%B9%E7%9B%AE%E5%9F%BA%E7%BA%BF%E7%9B%AE%E5%BD%95-%E6%96%B9%E4%BE%BF%E9%A1%B9%E7%9B%AE%E7%AE%A1%E7%90%86%E4%B8%8E%E4%BA%A4%E6%8E%A5/">项目基线目录-方便项目管理与交接</a>
    </h1>
  

      </header>
    
    <div class="article-entry" itemprop="articleBody">
      
        <p><strong>基线 (Baseline)</strong> 说起 .  基线是软件文档或源码 ( 或其它产出物 ) 的一个稳定版本 , 它是进一步开发的基础 . 所以 , 当基线形成后 , 项目负责 SCM 的人需要通知相关人员基线已经形成 , 并且哪儿可以找到这基线了的版本 . 这个过程可被认为内部的发布 . 至于对外的正式发布 , 更是应当从基线了的版本中发布 .  </p>
<p>​    基线是项目储存库中每个工件版本在特定时期的一个 “ 快照 ” 。它提供一个正式标准，随后的工作基于此标准，并且只有经过授权后才能变更这个标准。建立一个初始基线后，以后每次对其进行的变更都将记录为一个差值，直到建成下一个基线。</p>
<p>​    参与项目的开发人员将基线所代表的各版本的目录和文件填入他们的工作区。随着工作的进展，基线将合并自从上次建立基线以来开发人员已经交付的工作。变更一旦并入基线，开发人员就采用新的基线，以与项目中的变更保持同步。调整基线将把集成工作区中的文件并入开发工作区。</p>
<p>​    建立基线的三大原因是：<strong>重现性、可追踪性和报告。</strong></p>
<p>​    重现性是指及时返回并重新生成软件系统给定发布版的能力，或者是在项目中的早些时候重新生成开发环境的能力。可追踪性建立项目工件之间的前后继承关系。其目的在于确保设计满足要求、代码实施设计以及用正确代码编译可执行文件。报告来源于一个基线内容同另一个基线内容的比较。基线比较有助于调试并生成发布说明。</p>
<p>​    建立基线后，需要标注所有组成构件和基线，以便能够对其进行识别和重新建立。</p>
<blockquote>
<p>建立基线有以下几个优点：</p>
</blockquote>
<ul>
<li>基线为开发工件提供了一个定点和快照。  </li>
<li>新项目可以从基线提供的定点之中建立。作为一个单独分支，新项目将与随后对原始项目（在主要分支上）所进行的变更进行隔离。  </li>
<li>各开发人员可以将建有基线的构件作为他在隔离的私有工作区中进行更新的基础。  </li>
<li>当认为更新不稳定或不可信时，基线为团队提供一种取消变更的方法。  </li>
<li>您可以利用基线重新建立基于某个特定发布版本的配置，这样也可以重现已报告的错误。  </li>
</ul>
<blockquote>
<p>使用  </p>
</blockquote>
<p>​    定期建立基线以确保各开发人员的工作保持同步。但是，在项目过程中，应该在每次迭代结束点（次要里程碑），以及与生命周期各阶段结束点相关联的主要里程碑处定期建立基线：</p>
<p>​    生命周期目标里程碑（先启阶段）<br>​    生命周期构架里程碑（精化阶段）<br>​    初始操作性能里程碑（构建阶段）<br>​    产品发布里程碑（产品化阶段）</p>
<blockquote>
<p>基线目录结构</p>
</blockquote>
<p>​    <img src="img1.png" alt="项目目录结构"></p>
<p>克隆地址 <a href="https://github.com/shanyutou/Project-Baseline-Catalogue.git" target="_blank" rel="noopener">https://github.com/shanyutou/Project-Baseline-Catalogue.git</a></p>
<p>项目中同时不断积累各种文档模板与画图方法，目的在于提高程序员的文档能力。</p>

      
    </div>
    <footer class="article-footer">
      <a data-url="https://bianchengme.gitee.io/2020/10/11/%E9%A1%B9%E7%9B%AE%E5%9F%BA%E7%BA%BF%E7%9B%AE%E5%BD%95-%E6%96%B9%E4%BE%BF%E9%A1%B9%E7%9B%AE%E7%AE%A1%E7%90%86%E4%B8%8E%E4%BA%A4%E6%8E%A5/" data-id="cktcaskit005p3gucc4az83pv" class="article-share-link">Share</a>
      
      
    </footer>
  </div>
  
</article>


  
    <article id="post-Spring-boot-api-生成工具-apiDoc-vs-swagger-and-JApiDocs" class="article article-type-post" itemscope itemprop="blogPost">
  <div class="article-meta">
    <a href="/2020/09/09/Spring-boot-api-%E7%94%9F%E6%88%90%E5%B7%A5%E5%85%B7-apiDoc-vs-swagger-and-JApiDocs/" class="article-date">
  <time datetime="2020-09-09T14:19:23.000Z" itemprop="datePublished">2020-09-09</time>
</a>
    
  </div>
  <div class="article-inner">
    
    
      <header class="article-header">
        
  
    <h1 itemprop="name">
      <a class="article-title" href="/2020/09/09/Spring-boot-api-%E7%94%9F%E6%88%90%E5%B7%A5%E5%85%B7-apiDoc-vs-swagger-and-JApiDocs/">Spring boot api 生成工具 apiDoc vs swagger and JApiDocs</a>
    </h1>
  

      </header>
    
    <div class="article-entry" itemprop="articleBody">
      
        <blockquote>
<p>曾经看过这样一个笑话：程序员最讨厌写文档，比这个还讨厌的事情就是，别人居然不写文档。</p>
</blockquote>
<p>文档的确是个令猿头疼的东西哇，但是文档的重要性也是不言而喻。这里就给大家安利两款比较好用的接口文档生成工具：</p>
<ul>
<li>apidoc 比较好用的静态生成工具，生成静态api文档非常合适；</li>
<li>swagger功能强大的动态api工具，带有调试功能，knife4j是对swaggerUI封装的优化，更适合国人风格；</li>
<li>Japidocs 新开源的静态生成工具用的人虽然没有上面两个多，但是只需要java环境即可，也是不错的api生成工具，值得尝试。</li>
</ul>
<h3 id="1-apidoc"><a href="#1-apidoc" class="headerlink" title="1. apidoc"></a>1. apidoc</h3><h5 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h5><p>apidoc是一款可以由源代码中的注释直接自动生成api接口文档的工具，它几乎支持目前主流的所有风格的注释。</p>
<h5 id="使用"><a href="#使用" class="headerlink" title="使用"></a>使用</h5><p>首先你的环境必须要安装了node.js.然后通过以下的命令安装apidoc：</p>
<blockquote>
<p>npm install apidoc -g</p>
</blockquote>
<p>在你的项目根目录下添加apidoc.json文件，这个文件主要包含一些项目的描述信息，例如标题、介绍、版本等。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  &quot;name&quot;: &quot;example&quot;,</span><br><span class="line">  &quot;version&quot;: &quot;0.1.0&quot;,</span><br><span class="line">  &quot;description&quot;: &quot;apiDoc basic example&quot;,</span><br><span class="line">  &quot;title&quot;: &quot;Custom apiDoc browser title&quot;,</span><br><span class="line">  &quot;url&quot; : &quot;https:&#x2F;&#x2F;api.github.com&#x2F;v1&quot;</span><br><span class="line">&#125;</span><br><span class="line">1234567</span><br></pre></td></tr></table></figure>

<p>在你的代码注释里加入apidoc的注解，例如这样子：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">    &#x2F;**</span><br><span class="line">     * @apiGroup Product</span><br><span class="line">     * @api &#123;GET&#125; &#x2F;product&#x2F;&#123;id&#125;  查询一个产品</span><br><span class="line">     * @apiDescription 指定产品id , 删除产品的全部信息，包括关联的schema</span><br><span class="line">     * @apiParam &#123;String&#125; id 产品id(必填*)</span><br><span class="line">     * @apiSuccessExample SuccessExample</span><br><span class="line">     * HTTP&#x2F;1.1 200</span><br><span class="line">     * &#123;</span><br><span class="line">     * id: &#39;xxx&#39;,</span><br><span class="line">     * modelId: &#39;xxxxx&#39;,</span><br><span class="line">     * name: &#39;xxx&#39;,</span><br><span class="line">     * intro: &#39;xxxx&#39;</span><br><span class="line">     * &#125;</span><br><span class="line">     * @apiErrorExample ErrorExample</span><br><span class="line">     * HTTP&#x2F;1.1 600</span><br><span class="line">     * 具体的异常信息</span><br><span class="line">     *&#x2F;</span><br><span class="line">    @GetMapping(&quot;&#x2F;&#123;id&#125;&quot;)</span><br><span class="line">    public Product getOneProduct(@PathVariable String id)</span><br><span class="line">    &#123;</span><br><span class="line">        return productServ.findOne(id);</span><br><span class="line">    &#125;</span><br><span class="line">12345678910111213141516171819202122</span><br></pre></td></tr></table></figure>

<p>常用的一些注解如下：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">@api 定义API的请求方法、路径和名字</span><br><span class="line">@apiDescription 定义API的描述</span><br><span class="line">@apiGroup 定义API的分组</span><br><span class="line">@apiParam 定义API的参数</span><br><span class="line">@apiParamExample 参数请求的事例</span><br><span class="line">@apiVersion 版本</span><br><span class="line">@apiErrorExample API错误示例</span><br><span class="line">@apiSuccessExample API正常示例</span><br><span class="line">12345678</span><br></pre></td></tr></table></figure>

<p>然后就可以利用apidoc的命令来生成接口文档了：<br><img src="https://note.youdao.com/yws/api/personal/file/3E3A42C7488F4B28A55A571E547E667E?method=download&shareKey=f4cb66a4dda2c461904f3d17ac6d480f" alt="img"></p>
<p>然后当前目录下会生成一个新的apidoc目录，就是新生成的接口文档文件。<br><img src="https://note.youdao.com/yws/api/personal/file/A32C6142B85F4DF5B6573298C86676F8?method=download&shareKey=0473103f88076c2f2e8890f7fca8a8f9" alt="img"></p>
<p>打开 index.html 即可看到生成的接口文档：<br><img src="https://note.youdao.com/yws/api/personal/file/953F265590674B73989A418C301FFE67?method=download&shareKey=09939a1241d68066e42a2e3a3faec42b" alt="img"></p>
<h3 id="2-swagger"><a href="#2-swagger" class="headerlink" title="2. swagger"></a>2. swagger</h3><p>在这里俺用 springBoot 整合swagger2 做了个Restful Api 的接口小demo</p>
<p><a href="https://github.com/caigoumiao/springboot-swagger" target="_blank" rel="noopener">github 源码传送门☛☛☛</a></p>
<p>首先项目中得添加swagger2的依赖：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">&lt;!-- https:&#x2F;&#x2F;mvnrepository.com&#x2F;artifact&#x2F;io.springfox&#x2F;springfox-swagger2 --&gt;</span><br><span class="line">&lt;dependency&gt;</span><br><span class="line">    &lt;groupId&gt;io.springfox&lt;&#x2F;groupId&gt;</span><br><span class="line">    &lt;artifactId&gt;springfox-swagger2&lt;&#x2F;artifactId&gt;</span><br><span class="line">    &lt;version&gt;2.8.0&lt;&#x2F;version&gt;</span><br><span class="line">&lt;&#x2F;dependency&gt;</span><br><span class="line">&lt;!-- https:&#x2F;&#x2F;mvnrepository.com&#x2F;artifact&#x2F;io.springfox&#x2F;springfox-swagger-ui --&gt;</span><br><span class="line">&lt;dependency&gt;</span><br><span class="line">    &lt;groupId&gt;io.springfox&lt;&#x2F;groupId&gt;</span><br><span class="line">    &lt;artifactId&gt;springfox-swagger-ui&lt;&#x2F;artifactId&gt;</span><br><span class="line">    &lt;version&gt;2.8.0&lt;&#x2F;version&gt;</span><br><span class="line">&lt;&#x2F;dependency&gt;</span><br><span class="line">123456789101112</span><br></pre></td></tr></table></figure>

<p>创建swagger2 的配置类：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">@Configuration</span><br><span class="line">@EnableSwagger2</span><br><span class="line">public class SwaggerConfig</span><br><span class="line">&#123;</span><br><span class="line">    public Docket createApi()&#123;</span><br><span class="line">        return new Docket(DocumentationType.SWAGGER_2)</span><br><span class="line">                .apiInfo(apiInfo())</span><br><span class="line">                .select()</span><br><span class="line">                .apis(RequestHandlerSelectors.basePackage(&quot;com.miao.springbootswagger&quot;))</span><br><span class="line">                .paths(PathSelectors.any())</span><br><span class="line">                .build();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    &#x2F;&#x2F; 创建api的基本信息</span><br><span class="line">    private ApiInfo apiInfo()&#123;</span><br><span class="line">        return new ApiInfoBuilder()</span><br><span class="line">                .title(&quot;springBoot 整合 Swagger2 实例&quot;)</span><br><span class="line">                .description(&quot;更多技术内容分享见博客：https:&#x2F;&#x2F;blog.csdn.net&#x2F;qq_24871519&quot;)</span><br><span class="line">                .termsOfServiceUrl(&quot;https:&#x2F;&#x2F;blog.csdn.net&#x2F;qq_24871519&quot;)</span><br><span class="line">                .version(&quot;1.0&quot;)</span><br><span class="line">                .build();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line">1234567891011121314151617181920212223</span><br></pre></td></tr></table></figure>

<p>为接口添加swagger的注解：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br></pre></td><td class="code"><pre><span class="line">@RestController</span><br><span class="line">@RequestMapping(&quot;&#x2F;user&quot;)</span><br><span class="line">public class UserController</span><br><span class="line">&#123;</span><br><span class="line">    private static Map&lt;String, User&gt; users&#x3D;new ConcurrentHashMap&lt;&gt;();</span><br><span class="line"></span><br><span class="line">    @ApiOperation(value &#x3D; &quot;添加用户&quot;, notes &#x3D; &quot;添加一条用户信息&quot;)</span><br><span class="line">    @ApiImplicitParam(name &#x3D; &quot;user&quot;, value &#x3D; &quot;用户详细实体user&quot;, required &#x3D; true, dataType &#x3D; &quot;User&quot;)</span><br><span class="line">    @PostMapping(&quot;&#x2F;add&quot;)</span><br><span class="line">    public User add(User user)&#123;</span><br><span class="line">        String id&#x3D;UUID();</span><br><span class="line">        while(users.containsKey(id))&#123;</span><br><span class="line">            id&#x3D;UUID();</span><br><span class="line">        &#125;</span><br><span class="line">        user.setId(id);</span><br><span class="line">        users.put(id, user);</span><br><span class="line">        return user;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    @ApiOperation(value &#x3D; &quot;获取一个用户&quot;, notes &#x3D; &quot;根据用户id获取用户信息&quot;)</span><br><span class="line">    @ApiImplicitParam(name &#x3D; &quot;id&quot;, value &#x3D; &quot;用户ID&quot;, required &#x3D; true, dataType &#x3D; &quot;String&quot;)</span><br><span class="line">    @GetMapping(&quot;&#x2F;&#123;id&#125;&quot;)</span><br><span class="line">    public User getOne(@PathVariable String id)&#123;</span><br><span class="line">        if(!users.containsKey(id))&#123;</span><br><span class="line">            return null;</span><br><span class="line">        &#125;</span><br><span class="line">        return users.get(id);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    @ApiOperation(value &#x3D; &quot;获取所有用户信息列表&quot;)</span><br><span class="line">    @GetMapping(&quot;&#x2F;&quot;)</span><br><span class="line">    public List&lt;User&gt; getUsers()&#123;</span><br><span class="line">        return new ArrayList&lt;&gt;(users.values());</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    @ApiOperation(value &#x3D; &quot;更新用户信息&quot;)</span><br><span class="line">    @ApiImplicitParam(name &#x3D; &quot;user&quot;, value &#x3D; &quot;用户信息实体&quot;,required &#x3D; true, dataType &#x3D; &quot;User&quot;)</span><br><span class="line">    @PutMapping(&quot;&#x2F;&quot;)</span><br><span class="line">    public User updateOne(User u)&#123;</span><br><span class="line">        User tmp &#x3D; users.get(u.getId());</span><br><span class="line">        if(tmp &#x3D;&#x3D; null)&#123;</span><br><span class="line">            return null;</span><br><span class="line">        &#125;</span><br><span class="line">        if(u.getName()!&#x3D;null)&#123;</span><br><span class="line">            tmp.setName(u.getName());</span><br><span class="line">        &#125;</span><br><span class="line">        if(u.getAge() !&#x3D; null)&#123;</span><br><span class="line">            tmp.setAge(u.getAge());</span><br><span class="line">        &#125;</span><br><span class="line">        return tmp;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    @ApiOperation(value &#x3D; &quot;删除用户&quot;)</span><br><span class="line">    @ApiImplicitParam(name &#x3D; &quot;id&quot;, value &#x3D; &quot;用户ID&quot;, required &#x3D; true, dataType &#x3D; &quot;String&quot;)</span><br><span class="line">    @DeleteMapping(&quot;&#x2F;&#123;id&#125;&quot;)</span><br><span class="line">    public void delete(@PathVariable String id)&#123;</span><br><span class="line">        if(users.containsKey(id))&#123;</span><br><span class="line">            users.remove(id);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    private String UUID()&#123;</span><br><span class="line">        return UUID.randomUUID().toString().replace(&quot;-&quot;, &quot;&quot;);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line">1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465</span><br></pre></td></tr></table></figure>

<p>上述都完成后，启动springBoot 项目，访问 <a href="http://localhost/" target="_blank" rel="noopener">http://localhost</a>:/swagger-ui.html 即可访问接口文档页面，具体页面如下：<br><img src="https://note.youdao.com/yws/api/personal/file/8D095CF9E7CA47259ACB4DA34075E844?method=download&shareKey=9c3d003719cc502734c3739bbd6d5221" alt="img"></p>
<p><img src="https://note.youdao.com/yws/api/personal/file/1C94F35AE9A24495A8E2D1699C376FF2?method=download&shareKey=f5202a26d0dfa4e0e7e3a84d1804703c" alt="img"></p>
<h3 id="3、JApiDocs是一个无需额外注解、开箱即用的SpringBoot接口文档生成工具"><a href="#3、JApiDocs是一个无需额外注解、开箱即用的SpringBoot接口文档生成工具" class="headerlink" title="3、JApiDocs是一个无需额外注解、开箱即用的SpringBoot接口文档生成工具"></a>3、JApiDocs是一个无需额外注解、开箱即用的SpringBoot接口文档生成工具</h3><p>JApiDocs是一个无需额外注解、开箱即用的SpringBoot接口文档生成工具。</p>
<p>编写和维护API文档这个事情，对于后端程序员来说，是一件恼人但又不得不做的事情，我们都不喜欢写文档，但除非项目前后端代码都是自己写的，否则API文档将是前后端协作中一个不可或缺的沟通界面。</p>
<p>既然不可避免，那就想办法弄个轮子吧。人生苦短，必须偷懒。</p>
<p>相比Swagger要写一堆注解，Spring RestDocs需要写测试用例，才能生成API文档。JApiDocs 具有无痛集成的特点，你只需花几分钟就能知道它怎么用了。</p>
<p>快速开始</p>
<p>要使得JApiDcos正确工作，你写的代码应该是像下面的样子的：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">* 用户接口</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@RequestMapping</span>(<span class="string">"/api/user/"</span>)</span><br><span class="line"></span><br><span class="line"><span class="meta">@RestController</span></span><br><span class="line"></span><br><span class="line">publicclassUserController&#123;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">    * 用户列表</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">*<span class="doctag">@paramlistForm</span></span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">    */</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@RequestMapping</span>(path =<span class="string">"list"</span>, method = &#123;RequestMethod.GET,  RequestMethod.POST&#125;  )</span><br><span class="line"></span><br><span class="line">publicApiResult&gt; list(UserListForm listForm)&#123;</span><br><span class="line"></span><br><span class="line">returnnull;</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">    * 保存用户</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">*<span class="doctag">@paramuserForm</span></span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">    */</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@PostMapping</span>(path =<span class="string">"save"</span>)</span><br><span class="line"></span><br><span class="line"><span class="function">publicApiResult <span class="title">saveUser</span><span class="params">(@RequestBodyUserForm userForm)</span></span>&#123;</span><br><span class="line"></span><br><span class="line">returnnull;</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p>我们给Controller类和方法加上必要的注释，给接口方法返回相关的对象类型。是的，这样JApiDocs就能解析到相关的接口信息了，就跟我们平时写的代码是差不多的，但要注意，你要通过@param来告诉JApiDocs接口的参数，但在IDE的帮助下，这个工作将是轻松愉悦的：</p>
<p>然后你在任意一个main入口方法执行下面的代码就可以生成文档了：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">DocsConfigconfig&#x3D; new DocsConfig();</span><br><span class="line"></span><br><span class="line">config.setProjectPath(&quot;your springboot project path&quot;); &#x2F;&#x2F; 项目根目录</span><br><span class="line"></span><br><span class="line">config.setProjectName(&quot;ProjectName&quot;); &#x2F;&#x2F; 项目名称</span><br><span class="line"></span><br><span class="line">config.setApiVersion(&quot;V1.0&quot;);      &#x2F;&#x2F; 声明该API的版本</span><br><span class="line"></span><br><span class="line">config.setDocsPath(&quot;your api docs path&quot;); &#x2F;&#x2F; 生成API 文档所在目录</span><br><span class="line"></span><br><span class="line">config.setAutoGenerate(Boolean.TRUE);  &#x2F;&#x2F; 配置自动生成</span><br><span class="line"></span><br><span class="line">Docs.buildHtmlDocs(config); &#x2F;&#x2F; 执行生成文档</span><br></pre></td></tr></table></figure>

<p>接下来你只管好好写代码，生成Api文档的工作就可以交给JApiDocs了，你不需要再为额外编写和维护文档而烦恼。</p>
<p>功能特性</p>
<p>1、代码即文档</p>
<p>JApiDocs是通过直接解析SpringBoot的源码语法来工作的，所以只要Controller的语法符合一定的代码规范，有合理的注释，就可以直接导出文档。</p>
<p>2、支持导出HTML</p>
<p>便捷的导航和接口查看界面；可本地预览，或者部署到HTTP服务器。推荐部署到服务器，方便前后端展开协作。</p>
<p>3、同步导出客户端Model代码</p>
<p>支持导出Android端的 Java 和iOS端的 Object C Model代码，减少前端程序员的重复编码工作。</p>
<p>4、更多特性</p>
<p>支持接口搜索；支持不同版本和英文文档；自定义扩展等。</p>
<p>简洁的文档</p>
<p>再好用的东西，如果没有文档说明，别人也无从入手。为了让大家尽快上手，JApiDocs准备了一份极简的文档说明，确保你在几分钟就能用上JApiDocs。</p>
<p>花5分钟不到就能认识一个提高工作效率的工具，让你把更多的时间花在更加有价值的事情上，你确认不看一下吗？</p>
<p>“</p>
<p>仓库地址：</p>
<p><a href="https://github.com/YeDaxia/JApiDocs" target="_blank" rel="noopener">https://github.com/YeDaxia/JApiDocs</a></p>
<p>中文文档：</p>
<p><a href="https://japidocs.agilestudio.cn/#/zh-cn/" target="_blank" rel="noopener">https://japidocs.agilestudio.cn/#/zh-cn/</a></p>

      
    </div>
    <footer class="article-footer">
      <a data-url="https://bianchengme.gitee.io/2020/09/09/Spring-boot-api-%E7%94%9F%E6%88%90%E5%B7%A5%E5%85%B7-apiDoc-vs-swagger-and-JApiDocs/" data-id="cktcaskhf00193gucfwfchn8i" class="article-share-link">Share</a>
      
      
    </footer>
  </div>
  
</article>


  


  <nav id="page-nav">
    
    <span class="page-number current">1</span><a class="page-number" href="/page/2/">2</a><a class="page-number" href="/page/3/">3</a><span class="space">&hellip;</span><a class="page-number" href="/page/6/">6</a><a class="extend next" rel="next" href="/page/2/">Next &amp;raquo;</a>
  </nav>

</section>
        
          <aside id="sidebar">
  
    
  <div class="widget-wrap">
    <h3 class="widget-title">Categories</h3>
    <div class="widget">
      <ul class="category-list"><li class="category-list-item"><a class="category-list-link" href="/categories/Docker/">Docker</a><ul class="category-list-child"><li class="category-list-item"><a class="category-list-link" href="/categories/Docker/Nacos/">Nacos</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/Docker/mysql/">mysql</a></li></ul></li><li class="category-list-item"><a class="category-list-link" href="/categories/JAVA/">JAVA</a><ul class="category-list-child"><li class="category-list-item"><a class="category-list-link" href="/categories/JAVA/JVM/">JVM</a></li></ul></li><li class="category-list-item"><a class="category-list-link" href="/categories/Markdown/">Markdown</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/Windows/">Windows</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/docker/">docker</a><ul class="category-list-child"><li class="category-list-item"><a class="category-list-link" href="/categories/docker/redis/">redis</a></li></ul></li><li class="category-list-item"><a class="category-list-link" href="/categories/flutter/">flutter</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/git/">git</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/iOS/">iOS</a><ul class="category-list-child"><li class="category-list-item"><a class="category-list-link" href="/categories/iOS/Objective-C/">Objective-C</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/iOS/Xcode/">Xcode</a></li></ul></li><li class="category-list-item"><a class="category-list-link" href="/categories/java/">java</a><ul class="category-list-child"><li class="category-list-item"><a class="category-list-link" href="/categories/java/%E5%A4%9A%E7%BA%BF%E7%A8%8B/">多线程</a></li></ul></li><li class="category-list-item"><a class="category-list-link" href="/categories/mysql/">mysql</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/vue/">vue</a></li></ul>
    </div>
  </div>


  
    
  <div class="widget-wrap">
    <h3 class="widget-title">Tags</h3>
    <div class="widget">
      <ul class="tag-list" itemprop="keywords"><li class="tag-list-item"><a class="tag-list-link" href="/tags/BUG/" rel="tag">BUG</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/Docker/" rel="tag">Docker</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/JAVA/" rel="tag">JAVA</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/JVM/" rel="tag">JVM</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/Markdown/" rel="tag">Markdown</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/Objective-C/" rel="tag">Objective-C</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/RunTime/" rel="tag">RunTime</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/Swift/" rel="tag">Swift</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/UI/" rel="tag">UI</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/Windows/" rel="tag">Windows</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/Xcode/" rel="tag">Xcode</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/docker/" rel="tag">docker</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/flutter/" rel="tag">flutter</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/git/" rel="tag">git</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/hook/" rel="tag">hook</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/iOS/" rel="tag">iOS</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/iOS%E7%9C%9F%E6%9C%BA%E8%BF%90%E8%A1%8C/" rel="tag">iOS真机运行</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/java/" rel="tag">java</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/mysl/" rel="tag">mysl</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/mysql/" rel="tag">mysql</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/nacos/" rel="tag">nacos</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/redis/" rel="tag">redis</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/volatile/" rel="tag">volatile</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/vue/" rel="tag">vue</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/%E4%BC%98%E5%8C%96/" rel="tag">优化</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/%E5%A4%9A%E7%BA%BF%E7%A8%8B/" rel="tag">多线程</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/%E5%AD%A6%E4%B9%A0%E8%AE%A1%E5%88%92/" rel="tag">学习计划</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/%E5%B7%A5%E5%85%B7/" rel="tag">工具</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/" rel="tag">并发编程</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/%E7%AE%97%E6%B3%95/" rel="tag">算法</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/%E7%BD%91%E7%BB%9C/" rel="tag">网络</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/%E9%83%A8%E7%BD%B2/" rel="tag">部署</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/%E9%9D%A2%E8%AF%95/" rel="tag">面试</a></li></ul>
    </div>
  </div>


  
    
  <div class="widget-wrap">
    <h3 class="widget-title">Tag Cloud</h3>
    <div class="widget tagcloud">
      <a href="/tags/BUG/" style="font-size: 10px;">BUG</a> <a href="/tags/Docker/" style="font-size: 10px;">Docker</a> <a href="/tags/JAVA/" style="font-size: 10px;">JAVA</a> <a href="/tags/JVM/" style="font-size: 10px;">JVM</a> <a href="/tags/Markdown/" style="font-size: 10px;">Markdown</a> <a href="/tags/Objective-C/" style="font-size: 16.67px;">Objective-C</a> <a href="/tags/RunTime/" style="font-size: 10px;">RunTime</a> <a href="/tags/Swift/" style="font-size: 10px;">Swift</a> <a href="/tags/UI/" style="font-size: 13.33px;">UI</a> <a href="/tags/Windows/" style="font-size: 10px;">Windows</a> <a href="/tags/Xcode/" style="font-size: 10px;">Xcode</a> <a href="/tags/docker/" style="font-size: 13.33px;">docker</a> <a href="/tags/flutter/" style="font-size: 13.33px;">flutter</a> <a href="/tags/git/" style="font-size: 10px;">git</a> <a href="/tags/hook/" style="font-size: 10px;">hook</a> <a href="/tags/iOS/" style="font-size: 20px;">iOS</a> <a href="/tags/iOS%E7%9C%9F%E6%9C%BA%E8%BF%90%E8%A1%8C/" style="font-size: 10px;">iOS真机运行</a> <a href="/tags/java/" style="font-size: 16.67px;">java</a> <a href="/tags/mysl/" style="font-size: 10px;">mysl</a> <a href="/tags/mysql/" style="font-size: 10px;">mysql</a> <a href="/tags/nacos/" style="font-size: 10px;">nacos</a> <a href="/tags/redis/" style="font-size: 10px;">redis</a> <a href="/tags/volatile/" style="font-size: 10px;">volatile</a> <a href="/tags/vue/" style="font-size: 10px;">vue</a> <a href="/tags/%E4%BC%98%E5%8C%96/" style="font-size: 10px;">优化</a> <a href="/tags/%E5%A4%9A%E7%BA%BF%E7%A8%8B/" style="font-size: 10px;">多线程</a> <a href="/tags/%E5%AD%A6%E4%B9%A0%E8%AE%A1%E5%88%92/" style="font-size: 10px;">学习计划</a> <a href="/tags/%E5%B7%A5%E5%85%B7/" style="font-size: 10px;">工具</a> <a href="/tags/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/" style="font-size: 10px;">并发编程</a> <a href="/tags/%E7%AE%97%E6%B3%95/" style="font-size: 10px;">算法</a> <a href="/tags/%E7%BD%91%E7%BB%9C/" style="font-size: 10px;">网络</a> <a href="/tags/%E9%83%A8%E7%BD%B2/" style="font-size: 10px;">部署</a> <a href="/tags/%E9%9D%A2%E8%AF%95/" style="font-size: 10px;">面试</a>
    </div>
  </div>

  
    
  <div class="widget-wrap">
    <h3 class="widget-title">Archives</h3>
    <div class="widget">
      <ul class="archive-list"><li class="archive-list-item"><a class="archive-list-link" href="/archives/2021/09/">September 2021</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2021/07/">July 2021</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2021/06/">June 2021</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2021/05/">May 2021</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2021/03/">March 2021</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2020/10/">October 2020</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2020/09/">September 2020</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2020/08/">August 2020</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2020/07/">July 2020</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2020/05/">May 2020</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2020/04/">April 2020</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2020/03/">March 2020</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2019/06/">June 2019</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2019/05/">May 2019</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2019/04/">April 2019</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2019/03/">March 2019</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2019/02/">February 2019</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2018/10/">October 2018</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2018/03/">March 2018</a></li></ul>
    </div>
  </div>


  
    
  <div class="widget-wrap">
    <h3 class="widget-title">Recent Posts</h3>
    <div class="widget">
      <ul>
        
          <li>
            <a href="/2021/09/07/JVM-%E9%80%83%E9%80%B8%E5%88%86%E6%9E%90%E4%BC%98%E5%8C%96/">JVM 逃逸分析优化</a>
          </li>
        
          <li>
            <a href="/2021/07/02/Nacos%E9%80%9A%E8%BF%87Docke%E9%83%A8%E7%BD%B2/">Nacos通过Docke部署</a>
          </li>
        
          <li>
            <a href="/2021/07/01/Docker%E5%AE%89%E8%A3%85mysql5-7/">Docker安装mysql5.7</a>
          </li>
        
          <li>
            <a href="/2021/07/01/Docker%E5%AE%89%E8%A3%85redis/">Docker安装redis</a>
          </li>
        
          <li>
            <a href="/2021/06/30/Java%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B-volatile%E5%85%B3%E9%94%AE%E5%AD%97%E7%90%86%E8%A7%A3/">Java并发编程-volatile关键字理解</a>
          </li>
        
      </ul>
    </div>
  </div>

  
</aside>
        
      </div>
      <footer id="footer">
  
  <div class="outer">
    <div id="footer-info" class="inner">
      &copy; 2021 jmme@foxmail.com<br>
      Powered by <a href="http://hexo.io/" target="_blank">Hexo</a>
    </div>
  </div>
</footer>
    </div>
    <nav id="mobile-nav">
  
    <a href="/" class="mobile-nav-link">Home</a>
  
    <a href="/archives" class="mobile-nav-link">Archives</a>
  
</nav>
    

<script src="//ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>


  
<link rel="stylesheet" href="/fancybox/jquery.fancybox.css">

  
<script src="/fancybox/jquery.fancybox.pack.js"></script>




<script src="/js/script.js"></script>




  </div>
</body>
</html>